1. 서론

1.1. Python 소개

Python은 컴퓨터 프로그래밍 언어다.

컴퓨터 프로그래밍 언어는 컴퓨터에게 여러 가지 일을 시킬 때 사용하는 언어다.

간결하고 알기 쉬운 문법과 강력한 기능을 가진다.

초보자 교육에서부터 웹 개발, 과학계산, 데이터 분석, 인공지능 등 다양한 분야에 활용할 수 있다.

1991년 네덜란드 프로그래머 휘도 판 로숨(Guido van Rossum)이 첫 버전을 발표했다.

현재 3.6이 최신 버전이다.

1.1.1. Python의 특징

1.1.2. Python으로 할 수 있는 것

1.1.3. 인기

2018년 프로그래밍 언어 인기 순위

과학계

arXiv는 물리, 수학, 컴퓨터 분야에서 정식 출판 전인 논문을 올리는 사이트이다.

2017년 10월 arXiv에서 언급된 딥러닝 프레임워크들을 보면 tensorflow, keras, theano, pythorch 등 다수가 Python 프레임워크다.

(출처: https://cran.r-project.org/web/packages/keras/vignettes/why_use_keras.html)

천문학 논문에서 언급된 소프트웨어/프로그래밍 언어

(출처: https://www.datacamp.com/community/blog/python-scientific-computing-case)

1.1.4. Python의 단점

1.2. Anaconda

Python은 공식 홈페이지에서도 무료로 다운 받을 수 있지만, 데이터 분석에는 많은 추가 패키지가 필요하다. 패키지들 중에는 윈도에서 설치가 까다로운 것들도 있다. 이런 문제를 피하기 위해 아나콘다를 설치한다. 아나콘다는 Python과 여러 패키지들을 쉽게 설치/관리할 수 있는 배포판이다.

1.2.1. 아나콘다 다운로드

아나콘다를 설치하려면 구글에서 "anaconda download"를 검색하거나 https://www.anaconda.com/download 에 직접 접속한다.

anaconda_homepage

운영체제(Windows, macOS, Linux)를 선택 후 Python 버전을 선택한다. Python 버전은 최신 버전인 3.6을 선택한다. 설치 파일을 받고, 실행을 시키면 설치가 시작된다.

1.2.2. 설치 파일 실행

install_1

Next 를 클릭하고,

1.2.3. 라이센스 동의

install_2

I Agree 를 클릭한다.

이후로는 윈도를 기준으로 설명한다. 맥이나 리눅스에서는 옵션 선택 없이 Next만 누르면 된다.

1.2.4. 설치 유형

install_3

Just me를 선택하고 Next 를 클릭한다.

1.2.5. 설치 위치

install_4

다음은 아나콘다 저장 경로를 선택한다. 경로를 바꾸지말고 Next 를 클릭한다.

1.2.6. 고급 설치 옵션

install_5

고급 설치 옵션을 선택한다. 아나콘다의 설치 경로를 PATH 환경 변수에 추가를 해주고 기본 Python으로 설정하는 옵션이다. 둘 다 체크를 해주고 Install 을 클릭한다.

1.2.7. VS Code 설치 여부

설치가 완료되면 VS Code 설치 여부를 묻는데, 이 때 Skip 을 클릭하면 된다.

1.3. 주피터 노트북 사용법

1.3.1. 시작하기

화면 띄우기

작업을 희망하는 폴더의 경로창을 클릭해, jupyter notebook 을 친다.

새로운 코맨드 창이 뜬다. 이 코맨드 창은 작업 기간 동안 끄지 않고 유지해야 주피터 노트북을 사용할 수 있다.

새로운 인터넷창 혹은 탭이 동시에 뜬다.

만약 새 창이 뜨지 않으면, 수동으로 코맨드창의 주소를 복사해서 붙여놓으면 된다.

새 파일 만들기

NewPython 3 를 클릭하면 새로운 파일을 만들 수 있다.

1.3.2. 파일명 바꾸기

파일명을 클릭하면 이름을 수정할 수 있다.

1.3.3. 모드 설명

편집 모드

셀의 테두리 색이 초록색이면 편집(edit) 모드라는 의미이다.

편집 모드일 때 셀에 내용을 칠 수 있다.

단축키:

명령 모드

셀의 테두리 색이 파란색이면 명령(command) 모드라는 의미이다.

명령 모드는 셀에 어떠한 명령을 내리고 싶을 때 사용한다. 일반적으로 code 모드에서 markdown 모드로 변환할때 많이 사용한다.

단축키:

1.3.4. 셀 조작하기

추가하기

셀을 추가하고 싶을 때는 셀 추가 아이콘(+) 을 클릭한다.

단축키:

제거하기

셀을 지우고 싶을때는 셀 제거 아이콘(가위)를 클릭한다.

단축키:

셀 복사하기

셀을 복사하고 싶을때는 복사하기 버튼을 누른다.

단축키: 명령모드 + c

셀 붙여넣기

셀을 붙여넣고 싶을때는 붙여넣기 버튼을 누른다.

단축키:

셀 위아래로 이동

셀을 위아래로 이동할 수 있다. 명령모드 에서 화살표 버튼 을 클릭하면 셀을 이동시킬 수 있다.

셀 합치기

명령모드에서 shift를 같이 누르고 Edit - Merge Cell 을 누르면 두 셀을 합칠 수 있다.

단축키:

1.3.5. 코드 실행

코드 입력

편집모드에서 코드를 입력할 수 있다. 코드를 실행시키고 싶으면, 실행하기 버튼을 누르면 실행된다.

단축키:

전체 코드 실행

CellRun All 을 클릭하면 전체 셀이 실행된다.

멈추기

강제로 커널을 멈추고 싶다면, 멈추기 버튼 을 클릭한다.

단축키:

Home 에서 파일을 선택해 Shutdown 할 수도 있다.

결과보기

실행 결과가 길어질 경우, 결과의 왼쪽 회색 구역을 클릭함에 따라 여러 방법으로 결과를 볼 수 있다.

한번 클릭 은 펼쳐보기를 의미한다.

더블 클릭은 숨기기를 의미한다.

1.3.6. 주석 및 마크다운

제목 입력

주피터 노트북에 마크다운 모드에서 제목을 추가할 수 있다. 제목의 크기는 샵(#)의 개수로 정해진다.

단축키:

코드에 주석 추가

샵(#)으로 코드에 주석을 추가할 수 있다.

일반마크다운

한 셀을 마크다운 셀로 편집할 수 있다.

단축키:

1.3.7. 커널 재시작

커널을 재시작하고 싶을 경우, 커널 재시작 버튼 을 클릭해 Restart 버튼을 누른다.

단축키:

1.3.8. 파일 저장

주피터 파일은 자동 저장이 된다. 하지만, 바로 저장하고 싶을때는 저장 버튼을 누른다.

단축키:

1.3.9. 복사본 만들기

File - Make a Copy 로 파일의 복사본을 만들 수 있다.

복사본이 만들어졌다.

1.3.10. HTML로 내보내기

File - Download as - HTML 을 클릭하면, 주피터 노트북을 HTML 파일로 내보낼 수 있다.

HTML 파일은 다음과 같이 만들어진다.

1.3.11. .py 파일로 내보내기

File - Download as - Python 을 클릭하면, 주피터 노트북을 파이썬 파일로 내보낼 수 있다.

.py 파일은 파이썬 에디터로 수정하고 실행할 수 있다.

2. 자료형

2.1. 숫자

파이썬에서 수를 다루는 자료형에는 정수(int)와 실수(float) 두 가지가 있다.

2.1.1. 사칙연산

파이썬에서 계산은 일반 계산방법과 비슷하다.

더하기
2 + 4
  
6
빼기
2 - 4
  
-2
곱하기
2 * 4
  
8
나누기
2 / 4
  
0.5
제곱
2 ** 4  # 2의 4제곱
  
16
나머지
5 % 3  # 5÷3 의 나머지
  
2
5 / 2  # 5÷2 의 값
  
2.5
5 // 2  # 5÷2 의 몫
  
2

2.2. 변수

변수(variable)란 프로그래밍에서 값에 이름을 붙인 것이다.

파이썬에서 변수는 등호(=)로 할당한다.

a = 1  # 변수 지정
  
a  # 확인
  
1

2.2.1. 변수와 문자열 차이

초보자 단계에서 종종 변수와 문자열을 혼동할 수 있다.

철수 = 10 이라고 변수를 지정한 후 철수'철수' 의 차이를 알아보자.

철수 = 10
  print(철수)  # 철수
  print("철수")  # "철수"
  
10
  철수

첫번째 출력문은 10의 값을 가지는 변수 철수 의 값을 출력한다.

두번째 출력문은 문자열 "철수" 를 출력한다.

따옴표 " " 에 감싸쥔 내용은 문자열로 인식된다는 점을 잊지말자.

2.2.2. 이름 짓기 규칙

파이썬의 변수는 알파벳 대소문자, 숫자, 언더바(_) 그리고 한글, 한자 등을 사용할 수 있다. 단, 첫 글자는 숫자로 시작할 수 없다. 공백이나 특수문자, 문장 분호는 변수의 이름으로 쓸 수 없다.

일반적으로, 변수 이름은 영어로 짓는다. 이때 대소문자는 구별한다. 즉 Aa는 다른 변수이다.

var = 1
  
var
  
1

원한다면, 한글로도 지정이 가능하다.

변수 = 1
  
변수
  
1

언더바 (_)는 변수 이름이 길어질 때 주로 공백을 대신 해서 사용한다.

my_number = 1
  
my_number
  
1

변수 이름 첫 글자에 숫자를 쓸 수 없다.

1var = 1
  
File "<ipython-input-2-460332d99e8f>", line 1
      1var = 1
         ^
  SyntaxError: invalid syntax

이름에 하이픈(-) 을 사용할 수 없다.

var-1 = 1
  
File "<ipython-input-8-fad43703b7f0>", line 1
      var-1 = 1
               ^
  SyntaxError: can't assign to operator

2.2.3. 덮어쓰기

중복되는 변수가 있다면, 나중에 정의된 변수가 선택된다.

a = 1  # a에 1을 넣고
  
a = 100  # 같은 이름을 가진 변수에 100을 넣은 후,
  
a  # a 확인
  
100

나중에 지정된 100 이 저장됐다.

a = a + 1 표현

파이썬에서 다음 코드를 자주 보게된다.

a = 0  # a는 0
  a = a + 1  # a에 1을 더한 후, a라고 저장한다
  
a  # a를 확인해보면
  
1

결과는 0이 아닌 1이다.

a = a + 1  # 한번 더, a를 a 더하기 1이라고 저장한다.
  
a  # a를 확인해보면
  
2

2 라는 값을 갖는다.

2.2.4. 여러 변수 지정하기

한 번에 여러 변수를 지정할수 있다. 아래와 같이 입력할 경우 두 변수 ab에는 모두 같은 값 1이 할당된다.

a = b = 1
  
a
  
1
b
  
1

아래와 같이 입력할 경우에는 두 변수 a, b에 각각 1과 2가 할당된다.

a, b = 1, 2
  
a
  
1
b
  
2

아래와 같은 방식으로 두 변수의 값을 서로 맞바꿀 수도 있다.

a, b = b, a
  
a
  
2
b
  
1

2.2.5. Name Error

만약, 변수명을 정의하지 않고 실행시킨다면, NameError가 발생한다.

aa  # 정의하지 않은 변수
  
---------------------------------------------------------------------------
  NameError                                 Traceback (most recent call last)
  <ipython-input-19-9cd599a35238> in <module>()
  ----> 1 aa

  NameError: name 'aa' is not defined

이때는, 변수명을 다시 확인하거나, 주피터 노트북에서 실행시켰는지 다시 확인한다.

2.2.6. 변수 삭제

변수를 삭제하고 싶을때는, del 내장 함수를 사용한다.

a = 1  # a는 1이라고 지정한 후,
  
del a  # del 함수를 사용해 지워본다
  
a  # a를 확인해보면,
  
NameError                                 Traceback (most recent call last)
  <ipython-input-19-9cd599a35238> in <module>()
  ----> 1 a

  NameError: name 'a' is not defined

파이썬이 a 를 찾지 못했다고 NameError 를 띄운다.

2.2.7. 연습문제

철수는 100만원을 예금하고 연이율 5%의 복리로 10년간 예치하였다고 한다. 철수의 소지 금액은 매년 어떻게 변할지 구하시오.

1년 = 105
  2년 = 110.25
  ...

2.3. 문자

문자열은 문자로 이뤄진 자료 집합이다.

'This is a string'
  

2.3.1. 만드는 법

문자열은 따옴표를 사용해 만들거나,

a = '문자열입니다'
  b = "문자열입니다"
  

str 내장 함수로, 자료형을 바꿀 수 있다.

str(123)  # 정수를 문자열로
  
'123'

문자열 안에 따옴표를 사용할때는, 문자열이 중간에 끊겨 SyntaxError가 발생하지 않게 주의를 기울여야 한다.

"영희가 물었다. "철수야, 숙제했어?""  # 큰 따옴표 안에 큰 따옴표 사용
  
File "<ipython-input-31-b35db4e69346>", line 1
      "영희가 물었다. "철수야, 숙제했어?""
                  ^
  SyntaxError: invalid syntax

만약, 문자 열 안에 큰 따옴표를 사용하고 싶다면, 밖에 따옴표를 작은 따옴표로 감싸주면 된다.

'영희가 물었다. "철수야, 숙제했어?"'  # ' ' 로 " " 감싸기
  

따옴표 세개(""")를 연속으로 사용하면, 다시 따옴표 세 개가 연속으로 나올 때까지 줄바꿈을 포함해서 모든 글자를 문자열로 인식한다.

"""
  영희가 물었다. "철수야, 숙제했어?"
  철수는 생각했다. '영희는 숙제를 안하나?'
  """
  

2.3.2. 문자열 표현

줄 바꿈 등으로 문자열을 표현하고 싶다면, 역슬래쉬(\)를 사용할 수 있다.

'\n'  # 줄바꿈
  '\t'  # 탭
  

\n 을 출력할 경우, 줄바꿈으로 표현된다.

print('파이썬\n1. 문자열\n2. 숫자열')
  
파이썬
  1. 문자열
  2. 숫자열

2.3.3. 문자열 연산

덧셈 표시를 이용해, 문자열끼리 합칠 수 있다.

'1 더하기 1은 ' + '2 입니다'
  
'1 더하기 1은 2 입니다'

곱셈 표시를 이용해, 문자열을 반복할 수 있다.

'하' * 3
  
'하하하'

2.3.4. Format

문자열에 원하는 값을 대입할 수 있다.

예를 들어 "안녕하세요 OO님"에 "철수"를 대입할 수 있다.

만드는 법

문자열에 값을 여러 방법으로 대입할 수 있다.

.format()

문자열 안에 { } 를 넣어준 후, .format() 을 부르고 안에 원하는 값을 넣는다.

'제 키는 {}입니다'.format('180 센치')
  
'제 키는 180 센치입니다.'
'제 키는 {}이며, 몸무게는 {}입니다'.format('180 센치', '비밀')
  
'제 키는 180 센치이며, 몸무게는 비밀입니다.'

만약 변수명으로 문자열 삽입을 원한다면 변수명을 { } 안에 넣어줄 수 있다.

height = '비밀'
  weight = '180 센치'

  '제 키는 {height}이며, 몸무게는 {weight}입니다'.format(height=height, weight=weight)
  
'제 키는 180 센치이며, 몸무게는 비밀입니다.'
f''

파이썬 3.6 부터는 변수명으로 문자열 삽입을 할때 뒤에 .format() 을 붙이지 않고 f' ' 방법으로 직접 대입할 수 있다.

height = '비밀'
  weight = '180 센치'

  f'제 키는 {height}이며, 몸무게는 {weight}입니다'
  

2.3.5. 문자열 indexing, slicing

indexing

Indexing으로 원하는 자료에 접근할 수 있다.

a = '마인드스케일'  # 예시 문자열
  a[0]  # 문자열 첫 번째 값
  
'마'
a[3]  # 문자열 네 번째 값
  
'스'
text = 'hello world'  # 예시 문자열 
  text[0]  # 문자열 첫 번째 값
  
'h'
text[5]  # 텍스트의 여섯번째 값은,
  
' '

위의 예에서 보다시피, 공백도 문자열에 포함된다.

마이너스 기호는 뒤에서부터 숫자를 센다는 의미를 가지고 있다.

text[-1]  # 문자열 마지막 값
  
'd'
text[-2]  # 문자열 마지막에서 두 번째 값
  
'l'

slicing

Slicing으로 자료를 범위로 선택할 수 있다.

text[0:5]  # 첫 다섯 자료
  
'hello'
text[:5]  # 편의상 0 생략
  
'hello'
text[6:11]  # 7번째 12번째 자료
  
'world'
text[6:]  # 편의상 마지막 숫자 생략
  
'world'

2.3.6. 문자열 관련 함수

join

.join() 함수는 문자열을 특정 글자로 합쳐준다.

','.join('abcd')  # 'abcd' 사이에 쉼표를 추가
  
'a,b,c,d'

split

.split() 함수는 특정 글자로 문자열을 나눈다.

'사과,오렌지,포도,딸기'.split(',')  # 쉼표를 기준으로 문자열 분리
  
['사과', '오렌지', '포도', '딸기']

strip

.strip() 함수는 문자열 앞뒤의 공백을 지운다.

'    안녕하세요 '.strip()
  
'안녕하세요'

단, 문자열 내부의 빈 공백은 지우지 않는다.

'    안녕하  세요     '.strip()
  
'안녕하  세요'

replace

.replace() 함수는 특정 글자를 다른 글자로 바꿔준다.

'안녕하  세요'.replace('  ', '')  # 문자열 중간의 띄어쓰기 교체
  
'안녕하세요'
'파이쏜은 참 쉽습니다.'.replace('쏜', '썬')  # '쏜'을 '썬'으로 교체
  
'파이썬은 참 쉽습니다.'

in, not in

in을 사용해 문자열 안에 해당 글자가 포함되었는지 확인할 수 있다.

'사과' in '오늘의 메뉴는 밥, 미역국, 사과입니다.'  # '사과'가 문자열에 포함되었는지 확인
  
True

not in을 사용해 반대로 해당 글자가 없는지 확인할 수 있다.

'사과' not in '오늘의 메뉴는 밥, 미역국, 사과입니다.'  # '사과'가 문자열에 없는지 확인
  
False

count, find, index

.count() 함수는 해당 글자가 문자열에서 몇 번 쓰였는지 확인해준다.

text = 'hello world'
  text.count('l')  # 알파벳 'l' 이 몇번 쓰였는지 확인
  
3

.index() 함수는 해당 글자의 위치를 확인해준다.

text.index('e')  # 'e'의 위치 확인
  
1

capitalize, lower, upper

영문의 경우, 문자열을 대소문자로 바꾸는 함수들을 사용할 수 있다.

.capitalize() 함수는 문장의 첫 글자를 대문자로 바꾼다.

'hello world'.capitalize()
  
'Hello world'

.lower() 함수는 모든 글자를 소문자로 바꾼다.

'HeLLO woRlD'.lower()
  
'hello world'

.upper() 함수는 모든 글자를 대문자로 바꾼다.

'mBc, sBs, Kbs'.upper()
  
'MBC, SBS, KBS'

startswith, endswith

.startswith() 함수는 문자열이 해당 글자로 시작하는지 확인해준다.

'보고서 작성 중'.startswith('보고서')   # 문자열이 '보고서'로 시작하는지 확인
  
True

.endswith() 함수는 문자열이 해당 글자로 끝나는지 확인해준다.

'보고서 작성 중'.endswith('완료')  # 문자열이 '완료'로 끝나는지 확인
  
False

2.4. 자료형 변환

2.4.1. 정수로 변환

int 함수를 사용해 실수나 문자열을 정수로 만들 수 있다.

int(1.23)  # 실수를 정수로 변환
  
1
int('123')  # 문자열을 정수로 변환
  
123

2.4.2. 실수로 변환

float 함수를 사용해 정수나 문자열을 실수로 만들 수 있다.

float(5)  # 정수를 실수로 변환
  
5.0
float('1.23') - 0.1  # 문자열을 실수로 변환
  
1.13

2.4.3. 문자열로 변환

str 함수를 사용해 정수나 실수를 문자열로 만들 수 있다. 정수나 실수 이외에도 대부분의 파이썬 값은 str 함수를 이용해 문자열로 바꿀 수 있다.

str(5)
  
'5'
str(5.0)
  
'5.0'

3. 자료 구조

3.1. 리스트

리스트(list)는 값을 순서대로 저장하는 자료구조다.

[2, 3, 1]
  
[2, 3, 1]

3.1.1. 만드는 법

리스트는 값들을 대괄호([])로 감싸서 만들 수 있다.

a = [1, 2, 3]
  a
  
[1, 2, 3]

list 함수는 다른 값을 리스트 형태로 변환한다. 예를 들어 문자열을 list 함수에 넣으면 문자열이 글자들의 리스트로 바뀐다.

list('abc')
  
['a', 'b', 'c']

3.1.2. 길이 확인

리스트 안에 몇 개의 값이 들어있는지 확인하고 싶을때는 len 함수를 사용한다.

len(a)
  
3

3.1.3. 인덱싱

리스트 안에 있는 값을 선택하고 싶을 때는 인덱싱(indexing)을 사용한다. 인덱스는 0부터 시작한다.

x = ['a', 'b', 'c', 'd', 'e']
  
x[0]  # x의 첫 값
  
'a'
x[1]  # x의 두번째 값
  
'b'

뒤에서부터 셀 때는 마이너스 인덱스를 사용한다. -1은 뒤에서 첫번째라는 뜻이다.

x[-1]  # x의 뒤에서 첫 값
  
'e'

3.1.4. 슬라이싱

리스트에서 일정 범위의 값을 선택할 때는 슬라이싱(slicing)을 사용한다. 슬라이싱은 시작 인덱스:끝 인덱스 형식을 사용하는데, 시작 인덱스의 값은 포함하지만 끝 인덱스의 값은 포함하지 않는다.

x[0:3]  # 0번부터 2번까지 (3번은 포함하지 않는다)
  
['a', 'b', 'c']

끝 인덱스를 생략하면 시작 인덱스부터 끝까지 범위를 가리킨다.

x[3:]  # 3번부터 끝까지 선택
  
['d', 'e']

시작 인덱스를 생략하면 처음부터 끝 인덱스 전까지 범위를 가리킨다.

x[:3]  # 처음부터 2번까지
  
['a', 'b', 'c']

간격

시작 인덱스:끝 인덱스:간격의 형식으로 간격을 표시할 수도 있다.

x[::2]  # 처음부터 끝까지 2간격으로 선택
  
['b', 'd']
x[1:4:2]  # 1번 값부터 4번 값 사이에서, 2간격으로 선택
  
['b', 'd']

간격에 음수를 사용하면 결과가 역순이 된다. 리스트의 순서를 뒤집고 싶을때는, [::-1] 을 사용한다.

x[::-1]  # 리스트의 순서 뒤집기
  
['e', 'd', 'c', 'b', 'a']

3.1.5. 리스트 수정

데이터 추가하기

.append() 함수는 값을 리스트에 추가한다.

x = ['a', 'b', 'c', 'd', 'e']
  x.append('f')  # x에 f를 추가
  x
  
['a', 'b', 'c', 'd', 'e', 'f']

꺼내기

.pop() 함수는 하나의 값을 제거한다.

x.pop(-1)  # 마지막 값을 제거
  
'f'
x  # x 확인
  
['a', 'b', 'c', 'd', 'e']

리스트에서 값 제거

del 함수는 슬라이싱과 인덱싱을 사용해 제거가 가능하다.

del x[-1]  # 마지막 값 제거
  
x
  
['a', 'b', 'c', 'd']
del x[:2]  # 첫 두개의 값 제거
  x
  
['c', 'd']

찾아서 바꿔치기

.index() 함수는 값의 위치를 찾는다.

x = ['a', 'b', 'c', 'd', 'e']
  x.index('c')  # c의 위치
  
2

'c''k'로 바꾸고 싶다면, 인덱싱을 사용한다.

x[2] = 'k'  # 세 번째 값을 'k'로 지정
  
x
  
['a', 'b', 'k', 'd', 'e']

합치기

두 리스트를 더하기로 합칠 수 있다.

[1, 2, 3] + [4, 5, 6]
  
[1, 2, 3, 4, 5, 6]

반복하기

리스트의 내용을 곱하기로 반복할 수 있다.

x = [1, 2, 3]
  x * 2  # 두 번 반복
  
[1, 2, 3, 1, 2, 3]

3.2. 튜플

튜플(tuple)은 리스트와 같이 값을 순서대로 저장하는 자료 구조다. 리스트와 달리 튜플은 수정이나 변경을 할 수 없다.

(1, 2, 3)
  

3.2.1. 튜플의 존재 이유

튜플은 위치마다 정해진 의미가 있는 경우에 사용된다. 만약 평면상에서 좌표를 두 개의 수로 나타낸다고 해보자. 이 경우 0번 값은 가로 좌표, 1번 값은 세로 좌표와 같은 식으로 정해진 의미를 가진다. 여기에 값을 추가하거나 뺄 필요가 없기 때문에 이 경우엔 튜플을 쓴다.

만약 좌표를 리스트로 나타낼 수도 있다. 하지만 프로그램이 복잡해지면 리스트는 실수로 여기에 값을 추가하거나 뺄 수도 있다. 그러면 이 좌표에 가로와 세로 좌표가 모두 들어있을지 아니면 가로 좌표만 있을지 또는 가로와 세로 외에 다른 무엇이 들어있을지 장담할 수가 없게 된다.

튜플은 형태가 고정되어 있기 때문에 좀 더 안심하고 쓸 수 있다. 또한 추가/삭제/수정 등에 대비할 필요가 없기 때문에 리스트보다 약간 더 효율적인 내부 구조를 가지고 있다.

3.2.2. 만드는 법

튜플은 값들을 둥근 괄호로 감싼다.

(1, 2, 3)
  
(1, 2, 3)

tuple 함수에 다른 값을 넣으면 튜플로 변환한다. 예를 들어 리스트를 tuple 함수에 넣으면 튜플로 바뀐다.

tuple([1, 2, 3])
  
(1, 2, 3)

3.2.3. 인덱싱과 슬라이싱

튜플의 인덱싱과 슬라이싱은 리스트와 같다.

x = (1, 2, 3)
  x[0]  # 첫번째 값 선택
  
1

슬라이싱을 할 경우, 값이 튜플로 반환된다.

x[0:2]  # 첫 두 값 선택
  
(1, 2)

3.2.4. 합치기 & 반복하기

튜플 역시 더하기로 합치거나

y = (4, 5, 6)
  
x + y  # 두 튜플 합치기
  
(1, 2, 3, 4, 5, 6)

곱하기로 반복할 수 있다.

x * 3  # 세번 반복하기
  
(1, 2, 3, 1, 2, 3, 1, 2, 3)

3.2.5. 수정하기

튜플의 값을 수정하려 시도하면, TypeError가 발생한다.

x[0] = 'a'  # 첫 값을 'a'로 수정
  
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
  TypeError: 'tuple' object does not support item assignment

튜플의 값을 변경하고 싶다면, 튜플을 리스트로 바꾼 후, 다시 튜플로 만드는것도 한 방법이다.

x = list(x)  # 튜플을 리스트로 변환
  
x[0] = 'a'  # 원하는 값 수정
  x
  
['a', 2, 3]
x = tuple(x)  # 리스트를 다시 튜플로 변환
  
('a', 2, 3)

3.3. 사전

사전(dictionary)은 키(key)와 그에 대응하는 값을 짝지워놓은 자료 구조다. 사전이 단어에 해당하는 뜻을 찾기 위한 책인 것을 생각해보면 이런 이름이 붙은 이유를 짐작할 수 있을 것이다.

사전은 키와 값을 콜론(:)으로 짝짓고, 이들을 중괄호({})로 감싸서 표시한다.

fruit = {
      '사과': 10,
      '딸기': 20,
  }
  

사전을 키로 인덱싱 하면, 그에 대응하는 값을 돌려받을 수 있다.

fruit['사과']
  
10

기존의 값을 덮어 쓸 수도 있다.

fruit['사과'] = 100
  
fruit['사과']
  
100

사전에서는 키가 중복될 경우 먼저 나온 키는 무시한다.

repeated_dict = {'a': 1, 'a': 5, 'b': 10}  # key에 'a'가 두개 
  repeated_dict['a']
  
5

사전의 키에는 숫자, 문자, 튜플 등 불변(immutable)인 값만 넣을 수 있다. 리스트처럼 가변(mutable)적인 값은 키로 사용할 수 없다.

fruit[[1]]
  
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
  TypeError: unhashable type: 'list'

3.3.1. 관련 메소드

사전의 키를 모두 확인하고 싶다면, .keys() 메써드를 부른다.

fruit.keys()
  
dict_keys(['사과', '딸기'])

사전의 값을 모두 확인하고 싶다면,.values() 메써드를 부른다.

fruit.values()
  
dict_values([100, 20])

사전의 키와 값을 짝지어 보고 싶다면, .items() 메써드를 부른다.

fruit.items()
  
dict_items([('사과', 100), ('딸기', 20)])

3.3.2. 키의 존재

사전에 존재하지 않는 키를 찾으면, KeyError가 발생한다.

fruit['포도']  # 없는 key
  
Traceback (most recent call last)
  <ipython-input-134-ad8269a8288e> in <module>()
  ----> 1 fruit['포도']

  KeyError: '포도'

.get() 메소드는 사전에서 키로 찾아보고, 없으면 KeyError를 일으키는 대신 아무 것도 반환하지 않는다.

fruit.get('사과')  # 있는 key
  
100
fruit.get('포도')  # 없는 key
  

기본값을 설정하면 없는 키의 경우 기본값을 내보낸다.

fruit.get('사과', 500)  # 있는 key
  
100
fruit.get('포도', 500)  # 없는 key
  
500

in 으로 키가 사전에 있는지 확인할 수 있다.

'사과' in fruit
  
True

3.3.3. 삭제

del 함수를 사용해 값을 삭제할 수 있다.

del fruit['사과']  # '사과' 삭제
  fruit
  
{'바나나': 600, '오렌지': 200}

사전을 전부 비우고 싶다면, .clear() 메소드를 사용한다.

fruit.clear()
  fruit
  
{}

3.4. 집합

집합(set)은 수학의 집합과 같은 용도로 사용한다. 집합은 반복되는 값을 허용하지 않으며, 순서 또한 보존하지 않는다.

{1, 1, 3, 2}
  
{1, 2, 3}

3.4.1. 만드는 법

집합은 중괄호({})로 값을 감싸 만든다. 사전과 비슷하지만 키와 값의 짝이 없다는 점이 다르다.

x = {'사과', '포도', '바나나'}
  x
  
{'사과', '포도', '바나나'}

set 함수에 다른 값을 넣으면 집합으로 변환된다. 만약 겹치는 항목이 집합에 들어있다면, 반복되는 항목은 제거된다.

set(['사과', '사과', '포도', '바나나', '바나나', '사과'])
  
{'바나나', '사과', '포도'}

사전을 set에 넘기면 키의 집합으로 변환된다.

3.4.2. 길이

집합에도 len 함수를 사용할 수 있다.

len(x)
  
3

3.4.3. 추가 및 삭제

집합에 값을 추가하고 싶다면 .add()를 사용한다.

x.add('오렌지')  # 집합에 '오렌지' 추가
  x
  
{'바나나', '사과', '오렌지', '포도'}

하나 이상의 값은 .update()를 사용한다.

x.update(['수박', '딸기'])  # 집합에 '수박', '딸기' 추가
  x
  
{'딸기', '바나나', '사과', '수박', '오렌지', '포도'}

값을 제거하고 싶을때는 .remove()를 사용한다.

x.remove('딸기')  # '딸기' 제거
  
{'바나나', '사과', '수박', '오렌지', '포도'}

딸기가 제거되었다.

in 으로 값이 집합에 있는지 확인할 수 있다.

'바나나' in x
  
True

3.4.4. 집합 연산

집합끼리 연산이 가능하다.

a = {1, 2, 3}
  b = {3, 4, 5}
  

합집합

두 집합 사이의 합집합은 | 으로 구한다.

a | b
  
{1, 2, 3, 4, 5}

교집합

두 집합 사이의 교집합은 & 으로 구한다.

a & b
  
{3}

차집합

두 집합 사이의 차집합은 - 으로 구한다.

a - b
  
{1, 2}

4. 클릭 자동화

pyautogui를 사용하면, 클릭을 자동화할 수 있다.

클릭 자동화 중, 강제 종료시키고 싶을때 단축키는 다음과 같다:

import pyautogui
  

4.1. 사이즈

현재 화면의 크기를 가져온다.

width, height = pyautogui.size()
  
width, height
  
(1920, 1080)

4.2. 스크린샷

자동으로 전체화면 스크린샷을 가져와 pyautogui.png로 저장할 수 있다.

im1 = pyautogui.screenshot('pyautogui.png')
  

자동으로 부분 스크린샷을 가져올 수 있다. 이때 region 을 지정한다. 이를 pyautogui2.png 로 저장한다.

# region 에 left, top, width, height 형식으로 넘겨주면 된다.
  im2 = pyautogui.screenshot('pyautogui2.png', region=(900, 400, 500, 500))
  

4.3. 마우스 움직이기

moveTo 를 사용하면 마우스 포인터를 자동으로 움직일 수 있다.

pyautogui.moveTo(100, 300, duration=0.5)  # duration은 속도에 따라 마우스 움직임 차이
  

.dragTo 를 사용하면 마우스를 자동으로 드래그 할 수 있다.

pyautogui.dragTo(100, 300, duration=0.5)
  

현재 마우스 위치는 .position으로 알 수 있다.

pyautogui.position()
  
(281, 749)

현재 위치에서 마우스 포인터로 사각형을 만들어보자. 상대적인 위치는 .moveRel 을 사용한다.

pyautogui.moveRel(150, 0, duration=0.25)
  pyautogui.moveRel(0, 150, duration=0.25)
  pyautogui.moveRel(-150, 0, duration=0.25)
  pyautogui.moveRel(0, -150, duration=0.25)
  

4.4. 마우스 클릭

4.4.1. 시작 버튼 눌러보기

자동으로 시작버튼을 눌러보자.

pyautogui.click(24, 1065, duration=0.25)
  

4.4.2. 저장버튼 누르기

자동으로 주피터 노트북의 저장버튼을 눌러보자.

left, top = pyautogui.position() # 저장 버튼 위에 마우스를 위치하고 코드 실행
  
width, height = 20, 20
  

저장 버튼을 스크린샷한다.

im = pyautogui.screenshot('save_btn.png', region=(left, top, width, height))
  
x, y, _, _ = pyautogui.locateOnScreen('save_btn.png')
  pyautogui.click(x + 3, y + 4, duration=0.25)
  

4.5. 메모장 띄우기

자동으로 메모장을 띄워, 내용을 치도록 해보자. 단, 한국어는 입력이 안된다.

pyautogui.PAUSE = 0.8
  pyautogui.click(24, 1065, duration=0.25)  # 시작 버튼

  pyautogui.typewrite('memo')  # 메모장을 검색한다
  pyautogui.press('enter')

  # 메모장에 타자 치기
  pyautogui.typewrite('Hello World !')  # Hellow World! 를 적는다
  pyautogui.press('enter')
  pyautogui.press('enter')
  pyautogui.press('hanguel')  # 한/영 키보드를 누른다
  pyautogui.typewrite('dkssud tptkddk !')  # 안녕 세상아를 적는다
  pyautogui.press('enter')

  pyautogui.typewrite('안녕 세상아 !')   # 단, 한국어는 인식 안됨
  

memo

4.6. 엑셀 띄우기

4.6.1. 자료 입력하기

사용 금액을 엑셀에 자동으로 입력해보자.

import time
  
money = '10000'
  payment = 'card'  # 카드
  date = '02.22.18'
  
pyautogui.PAUSE = 1
  pyautogui.click(24, 1065, duration=0.25)   # 시작 버튼 클릭
  pyautogui.typewrite('excel')
  pyautogui.press('enter')

  time.sleep(3)
  pyautogui.press('enter')

  pyautogui.typewrite('amount')   # 금액

  pyautogui.press('tab')
  pyautogui.typewrite('payment')   # 결제 방법

  pyautogui.press('tab')
  pyautogui.typewrite('date')   # 날짜

  pyautogui.press('enter')
  pyautogui.typewrite(money)

  pyautogui.press('tab')
  pyautogui.typewrite(payment)

  pyautogui.press('tab')
  pyautogui.typewrite(date)

  pyautogui.press('enter')
  

excel

4.6.2. 데이터를 엑셀에 입력하기

데이터를 엑셀에 자동으로 입력해보자.

import pandas as pd
  

먼저 UCI data repository 에서 forest fires 데이터셋을 가져온다.

http://archive.ics.uci.edu/ml/datasets/Forest+Fires

df = pd.read_csv('forestfires.csv')
  
df = df[:10]
  
pyautogui.click(24, 1065, duration=0.25)  # 시작버튼 클릭
  pyautogui.typewrite('excel')
  pyautogui.press('enter')

  time.sleep(3)
  pyautogui.press('enter')

  pyautogui.PAUSE = 0.3

  pyautogui.typewrite('X')   # 엑셀에 작성
  pyautogui.press('tab')

  pyautogui.typewrite('temperature')
  pyautogui.press('enter')

  # 데이터에서 X 와 temp 항목을 입력한다
  for num, i in df.iterrows():
      pyautogui.typewrite(str(i['X']))
      pyautogui.press('tab')
      pyautogui.typewrite(str(i['temp']))
      pyautogui.press('enter')
  

5. 흐름 제어

5.1. for 문

for 문은 자료형에 들어있는 각 항목마다, 같은 코드를 반복할때 사용한다.

for <항목> in <자료>:
      <항목에 대한 코드>
  

예를 들어, 새해가 되어 각 사람들의 나이에 1 씩 더해야 할 때, for 문을 사용해 1을 더해주면, 코드가 더욱 간편해진다.

for age in [13, 17, 22]:  # 세 명의 사람의 나이가 13, 17 22 라고 할 때,
      print(age + 1)        # 세명 각각의 나이에 차례대로 1 씩 더한다.
  
14
  18
  23

여기서 for문안에 사용되는 age 는 리스트 안의 항목을 지칭하기 위한 일시적인 변수 이름이다.

age 를 실행시켜보면, for문의 마지막 값이 저장되어있다.

age
  
22

만약 for문 전에 age = 100 이라고 변수를 지정해준다면, for 문에서 사용되는 age에 의해 덮어쓰여진다.

age = 100

  for age in [13, 17, 22]:
      print(age + 1)
  
14
  18
  23

예제를 통해 더 자세히 알아보자.

name 이라는 변수에 과일 이름이 리스트로 들어있다.

name = ['사과', '포도', '딸기']
  

namefor 문에 넘겨준다.

for 문 다음의 문장은 들여쓰기를 해줘야한다.

for i in name:  # 'name' 의 각 항목을 i 라고 지정한 후,
      print(i)    # 들여쓰기가 되어있는 상태에서, i 를 출력한다.
  
'사과'
  '포도'
  '딸기'

name 안에 들어있는 항목들이 처음부터 끝까지 i 라는 변수에 대입이 되어 print 라는 코드를 실행하여 출력이 된다.

5.1.1. range

for 문을 사용할 때, 자주 사용되는 range 내장 함수에 대해서 알아보자.

range 는 말 그대로 범위를 뜻 한다.

range(5)  # range 5 를 실행시킨다.
  
range(0, 5)

결과로, range 자료형이 만들어진다.

이번엔, rangefor 문에 적용하여 결과를 확인하자.

for i in range(5):   # range 를 이해하기 쉽게, for 문으로 돌려, 
      print(i)         # 각 값을 출력한다.
  
0
  1
  2
  3
  4

결과를 확인하면, 0 부터 4 까지의 숫자가 출력된다.

이렇게 레인지는 0 부터 지정된 숫자까지 정수값들을 만든다.

이번에는 range 의 시작 숫자도 지정하자.

for i in range(1, 5):  # range 1 부터 5 까지 for 문으로 돌려, 
      print(i)           # i 를 출력하면,
  
1
  2
  3
  4

1 부터 4 까지의 숫자가 나온다.

이렇게 range 에 시작 숫자도 지정할 수 있습니다.

5.1.2. range step

range 함수를 통해 숫자를 일정 간격으로 셀 수도 있다.

range(0, 10, 2)  # 0 부터 10까지의 수를 2 간격으로 세는 range 함수
  

for 문을 통해 확인하자.

for i in range(0, 10, 2):    # for 문으로 0 부터 9 까지, 2 간격으로
      print(i)                # i 를 출력한다.
  
0
  2
  4
  6
  8

0 에서부터 9 까지 2 간격으로 숫자가 출력된다.

5.1.3. 1부터 10까지 더하기

이번에는 for 문을 사용해, 1부터 10 까지 더해보자.

먼저 합을 기록해놓을 total 이라는 변수를 0 이라고 저장한다.

total = 0
  
for i in range(1, 11):   # 1 부터 10 까지 숫자를 i 라고 지정한 후,
      total = total + i    # total 에 i 를 더해주고, 새로 total 이라고 저장한다.
      print(i, total)     # i 와 total 을 출력한다.
  
1 1
  2 3
  3 6
  4 10
  5 15
  6 21
  7 28
  8 36
  9 45
  10 55

i 가 1 씩 증가하고, totali 만큼 증가하는 것을 확인할 수 있다.

total 을 확인해보니 1부터 10 까지의 합인 55인 것을 알 수 있다.

total
  
55

5.1.4. Enumerate

for 문을enumerate 와 같이 사용하면, 각 항목의 값과 인덱스 값을 동시에 알 수 있다.

예시를 통해 더 자세히 알아보자.

name = ['사과', '포도', '딸기']
  for i, j in enumerate(name):
      print(i, j)
  
0 사과
  1 포도
  2 딸기

i 부분은 j 항목의 인덱스 값으로 출력된다.

만약 인덱스의 시작값을 바꾸고 싶다면, 원하는 시작값을 넘겨줄 수 있다.

예시로 인덱스값을 1 로 시작하게 바꿔보자.

name = ['사과', '포도', '딸기']
  for i, j in enumerate(name, 1):
      print(i, j)
  
1 사과
  2 포도
  3 딸기

5.1.5. for 문 들여쓰기

for 문을 사용한 반복 범위는 들여쓰기 내에만 해당된다.

a, b, c 를 출력해보자.

print('a')
  print('b')
  print('c')
  
a
  b
  c
  

for 문의 들여쓰기에 'a' 를 출력하도록 코드를 짜고 결과물을 예상해보자.

for i in range(3):
      print('a')
  print('b')
  print('c')
  

들여쓰기에 해당하는 'a' 만 반복되어 나온다.

a
  a
  a
  b
  c
  

이번에는 for 문의 들여쓰기에 'a''b'를 출력하도록 코드를 짜고 결과물을 예상해보자.

for i in range(3):
      print('a')
      print('b')
  print('c')
  

들여쓰기에 포함된 'a''b' 만 반복되고 들여쓰기 밖에 있는 'c' 는 반복되지 않는다.

a
  b
  a
  b
  a
  b
  c

이번에는 a, b, c 와 해당 숫자를 확인해보자.

for i in range(3):
      print(i)
      print('a')
      print('b')
      print('c')
  

각 반복 번호와 반복되는 내용을 확인할 수 있다.

0
  a
  b
  c
  1
  a
  b
  c
  2
  a
  b
  c

이번에는 반복 번호와 'a' 만 출력해서 들여쓰기에 구역에 넣어보자.

for i in range(3):
      print(i)
      print('a')
  print('b')
  print('c')
  

'a' 만 3 번 반복되는 모습을 볼 수 있다.

0
  a
  1
  a
  2
  a
  b
  c

만약 들여쓰기를 해주지 않으면 IndentationError 를 보게 된다.

for i in range(3):
  print(i)
  print('a')
  print('b')
  print('c')
  
File "<ipython-input-8-f681578deac8>", line 2
      print(i)
          ^
  IndentationError: expected an indented block

if 문과 함께 사용하기

for 문을 if 문과 같이 사용할 수 있다.

for x in range(3):
      if x % 3 == 0:  # x 가 3의 배수일 경우
          print('a')
      elif x % 3 == 1:  # x 가 3으로 나눴을 경우 나머지가 1 일 때
          print('b')
      else:  # x 가 3으로 나눴을 경우 나머지가 2 일 때
          print('c')
  
a
  b
  c

5.1.6. for 문 안에 for 문

for 문 안에 for 문을 넣을 수도 있다.

for a in range(1, 4):  # range 1 부터 4까지를 for 문에 넘겨주고,
      for b in range(1, 4):  # 또 다른 for 문을 입력한다.
          print('a-', a, 'b-', b)  # 첫 for 문의 항목 a 와 두번째 항목인 b 를, 짝지어서 출력
  
a- 1 b- 1
  a- 1 b- 2
  a- 1 b- 3
  a- 2 b- 1
  a- 2 b- 2
  a- 2 b- 3
  a- 3 b- 1
  a- 3 b- 2
  a- 3 b- 3

a 가 1일 때, b 는 1 에서 3 까지 돈다.

그 후에 a 가 2 로 넘어가고, b 는 다시 1 에서 3 까지 돈다.

5.1.7. 구구단

이번에는 for 문 안에 for 문을 사용해, 구구단을 만들어보자.

예를 들어, 아래와 같이 2 곱하기 1이 있다면,

2 * 1  # 첫번째 포문은 왼쪽 숫자를, 두번째 포문은 오른쪽 숫자를 나타내도록 한다.
  

첫 번째 for 문은 2 단부터 9 단 까지 수행하기 위해, 바깥 for 문에 range 2와 10 을 넘겨주고 각 항목을 i 라고 부른다.

안쪽 for 문에는 range 1 과 10 을 넘겨주고, 각 항목을 j 라고 부른다.

for i in range(2, 10):
      for j in range(1, 10):
          print(i * j)     # i 와 j 의 곱을 출력한다.
      print('------')      # 큰 포문이 하나 끝날때마다 구분선을 그어주도록 한다.
  

2단 부터 9 단 까지의 구구단이 출력된다.

5.1.8. 반복될 수 있는 자료 유형 (list, set, dictionary, tuple)

for i in tuple((1, 2, 3)):  # for 문에는 list 외에도, tuple 이나,
      for j in set((1, 2, 3)):  # set 등을 넘겨줄 수도 있다. 
          print(i, j)
  
1 1
  1 2
  1 3
  2 1
  2 2
  2 3
  3 1
  3 2
  3 3

dictionary 의 경우, dictionary.keys() 등의 methods 를 활용해, for 문을 돌 수 있다.

for i in dict(a=1, b=2, c=3).keys():
      print(i)
  
a
  b
  c

5.1.9. 연습문제

홀짝 출력하기

이번에는 for 문 안에 이프문을 사용해 홀수와 짝수를 다르게 출력하는 코드를 만든다.

for i in range(1, 11):   # 1 에서 11 전 까지 for 문을 돌려, 각 항목을 i 라고 지정한다.
      if i % 2 == 0:       # 이프문을 사용해 만약 i 를 2 로 나눌 때, 나머지가 0 이면,
          print(i, '- 짝수입니다.')    # i 와 '짝수입니다' 를 출력한다.
      else:                             # i 를 2로 나눌 때, 나머지가 0 이 아니라면,
          print(i, '- 홀수입니다.')    # i 와 '홀수입니다' 를 출력한다.
  
1 - 홀수입니다.
  2 - 짝수입니다.
  3 - 홀수입니다.
  4 - 짝수입니다.
  5 - 홀수입니다.
  6 - 짝수입니다.
  7 - 홀수입니다.
  8 - 짝수입니다.
  9 - 홀수입니다.
  10 - 짝수입니다.

1 에서 10 까지 홀수와 짝수인지가 출력되었다.

5.2. if else

python 에서 조건에 따라 다른 코드를 실행하고 싶을때, if 문을 사용한다.

if 문은 기본적으로 조건문과 실행할 블록으로 이루어져 있다.

if 문 안에 들어있는 블록은 조건문이 참일 경우에만 실행됩니다.

if <조건문>:
    <블록>
  

5.2.1. True/False 조건문

예시로 자세하게 알아보자.

a = True
  if a:
      print('a is true')
  
a is true

위 코드를 실행하면, "a is true" 라는 문구가 출력된다.

반대로 b에 False 를 넣어주면,

b = False
  if b:
      print('b is true')
  

b 가 참이 아니기 때문에, 아무것도 출력되지 않는다.

5.2.2. 숫자 조건문

이번엔 숫자로 된 예시를 확인하자.

a = 10
  if a > 5:
      print('large')
  
large

a 가 5 보다 크기 때문에 'large' 가 출력이 된다.

b = 2
  if b > 5:
      print('large')
  

'b > 5' 의 결과가 False 이기 때문에, 아무것도 출력이 되지 않는다.

5.2.3. else

만약 조건문이 충족되지 않았을 경우, 다른 코드를 실행시키고 싶다면, else 를 사용하면 된다.

if <조건문>:
      <참일 경우>
  else:
      <거짓일 경우>
  

이전의 예시를 다시 살펴보자.

b = 2

  if b > 5:            # b 가 5보다 크면,
      print('large')   # 라지를 출력하고,
  else:                  # 크지 않으면,
      print('small')   # 스몰을 출력하게 합니다.
  
small

if 문에 조건에 충족되지 않았기 때문에, else 문에 있는 print('small') 코드가 실행 되었다.

또다른 예시를 살펴보자.

x3의 배수이면 'a' 를 출력하도록 if 문을 짜보자.

x = 3

  if x % 3 == 0:
      print('a')
  else:
      print('d')
  

x 의 값이 3 이기 때문에 a 가 출력된다.

a

5.2.4. elif

if 문은 하나의 조건일 경우에만 사용할 수 있었다.

만약 또 다른 조건에 따라 블록을 지정하고 싶다면, elif 문을 사용하면 된다.

elif 는 else if 의 줄임말이다.

if <조건문>:
      <참일 경우>
  elif < 다른 조건문>:
      < 다른 조건문이 참일 경우>
  

i 가 2 일 경우는 'two two', 3 일 경우는 'three three' 를 출력하는 코드를 생성해보자.

i = 3

  if i == 2:
      print('two two')
  elif i == 3:
      print('three three')
  else:
      print('.')
  
three three

i 가 3 일경우 elif 부분이 실행되어서 'three three' 가 출력되었다.

5.2.5. if 예시

elif는 여러개 사용할 수 있다.

if 문 연습을 위해 나이에 따라 호칭을 지정하는 프로그램을 만들어보자.

age = 28     # 예를 들어 어떤 사람의 나이가 28세 라고 치자.
  
if age < 20:
      print('미성년')
  elif age < 40:
      print('청년')
  elif age < 65:   # 또 다른 elif
      print('중년')
  else:
      print('노인')
  
청년

if 문을 돌려보면, 나이 28 은 청년으로 불리게 된다.

5.2.6. 연습문제

if 안에 if

if 문 안에, if 문을 중복해서 사용할 수 있다.

이전과 같은 호칭을 지정하는 프로그램에서 성별 옵션도 추가하자.

나이가 65세 이상인데, 남성이면 '할아버지', 여성이면 '할머니'로 결과를 출력하도록 하자.

age = 80             # 예를 들어 어떤 사람의 나이가 80세 이고,
  gender = 'male'        # 성별은 '남성' 이라고 치자.
  
if age < 20:
      print('미성년')
  elif age < 40:
      print('청년')
  elif age < 65:
      print('중년')
  else:                     # else 문이 실행되면, 그 안의 if 문이 실행된다.
      <작성해야할 부분>
  
할아버지

실행결과, 나이가 80 이고 성별이 남성이면 할아버지라고 불리게 된다.

5.3. 비교연산

5.3.1. True/False

python 에 불리언 (Boolean) 자료형이 있다. 불리언 (Boolean) 이란 참과 거짓, 두가지 값만 가지는 자료형을 의미한다.

a = True  # a 를 트루라고 저장한다.
  
a         # a 를 실행시켜보면,
  
True

참을 내보낸다.

type(a)
  
bool

a 의 타입을 살펴보면, 불리언이라고 출력된다.

5.3.2. 비교연산

불리언은 특히, 비교연산에서 사용한다.

python 에서 좌우 항목을 비교하기 위해, 여러가지 비교연산자를 허용한다.

<항목A> <비교연산자> <항목B>
  

이번 강의에서는, 비교 연산자에 대해 알아보자.

==

좌 우의 값이 동일하다 는 표현을 사용하고 싶을 때는, = 표시를 두 개 사용한다.

2 와 2 의 값이 같다 를 python 으로 표현해보자.

2 == 2
  
True

실행시켜본 결과, 참인것을 알 수 있다.

!=

좌우의 값이 같지 않다는 표현을 사용하고 싶을 때는 != 표시를 사용한다 .

2 != 4
  
True

24가 같지 않다는 것을 의미하는 2 != 4를 실행시키면, 결과가 참으로 출력된다.

<, >

값이 더 크거나 더 작다를 표현하고 싶을때는<,> 를 사용한다.

2 < 4
  
True
2 > 4
  
False

<=, >=

값을 포함해, 크거나 같다를 표현하고 싶을때 <, > 와 함께 = 를 사용한다.

2 <= 2
  
True
1 >= 2
  
False

5.3.3. 숫자 외의 데이터

숫자형 데이터 외에도, 비교연산을 문자열이나 리스트 등에도 적용시킬 수 있다.

'sam' == 'sam'
  
True

리스트 끼리 비교해보자.

a = [1, 2, 3]
  b = [1, 2, 3]
  a == b
  
True

비교할때, 좌우 데이터형이 같아야한다.

3 >= [1, 2]
  
TypeError                                 Traceback (most recent call last)
  <ipython-input-18-d947d032a41d> in <module>()
  ----> 1 3 >= [1, 2]

  TypeError: '>=' not supported between instances of 'int' and 'list'

예를 들어, 숫자 312 가 들어있는 리스트 보다 크거나 같다고 표현해보면, integerlist 를 비교할 수 없다는 TypeError 가 발생한다.

5.4. 논리연산

python 에서는 영어 문장에서 쓰이는 and, or, not 과 같은 논리연산이 그대로 쓰인다.

and, or, not
  

이 강의에는 and, or, not 을 다룬다.

5.4.1. and

and 는 좌우 항목이 모두 True 일 경우에만, True 라는 결과를 갖는다.

<항목A> and <항목B>
  

예시를 한번 확인해보자.

'sam' == 'sam' and 8 / 2 == 4
  
True

'sam' 과 또 다른 'sam' 이 같기 때문에 and 의 왼쪽 항목은 True 이며,

8 / 24 와 같기 때문에, and 의 오른쪽 항목도 True 이다.

and 의, 좌우가 모두 True 이기 때문에, 결과가 True 이다.

이번에는 'sam''cam' 으로 바꿔서 실행시켜 보자.

'cam' == 'sam' and 8 / 2 == 4
  
False

결과는 False 로 출력된다.

and 의 왼쪽과 오른쪽 항목 모두 True 일 경우에만 and 의 값이 True인 것을 확인할 수 있다.

5.4.2. or

or 는 왼쪽과 오른쪽 항목 중, 하나만 True 여도 True 값을 갖는다.

<항목A> or <항목B>
  
('cam' == 'sam') or (8 / 2 == 4)
  
True

or 양 옆의 두 항목 중, 8 / 2 == 4 라는 항목 하나가 True 이기 때문에 True 로 출력된다.

왼쪽과 오른쪽 항목 모두 False 인 경우를 제외하고는, 결과값이 모두 True 로 나온다.

5.4.3. not

not 은 무엇이 아니다 라는 부정의 의미를 갖고 있다. 참인 값을 거짓으로 뒤집어주고, 거짓인 값을 참으로 뒤집어준다.

not
  
1 == 3
  
False

13은 같지 않기 때문에 결과가 False 이다.

하지만 1 은 3 이 아니다 라고 not 을 써주면,

not (1 == 3)
  
True

True 값을 갖는다.

not 은 등호가 필요한 비교 연산에는 사용할 수 없다.

1 not > 2
  
File "<ipython-input-90-ebf613fb3e62>", line 1
      1 not > 2
            ^
  SyntaxError: invalid syntax

5.4.4. in

in 은 어디 안에 들어있다 는 뜻이다.

예를 들어, 4 가 리스트 안에 들어있다 고 써주면,

4 in [1, 2, 3]
  
False

4 가 리스트 안에 없기 때문에, 결과가 False 로 출력된다.

notin 과 함께 쓸 수도 있다.

in 앞에 not 을 붙여주면 들어있지 않다 는 것이다.

4 not in [1, 2, 3]
  
True

이번엔 결과가 True 로 나온다.

5.5. while

while 문은 어느 조건이 '참' 일 동안, 블록을 반복한다.

while <조건>:  # 조건이 참 이라면
      <블록>     # 블록내에 코드를 실행한다.
  

5.5.1. while 예시

예시로 while 문을 이용해 0 에서 4 까지 출력하도록 하자.

while 문은 조건이 참일동안 계속 돌아간다. 따라서, for 문과 달리 무한 반복에 빠지지 않도록 주의해야한다.

i = 0  #  while 문을 몇 회 도는지 추적하기 위해, while 문 밖에 i 는 0 이라고 지정한다.

  while i < 5:   # i 가 5 보다 작을 때까지, while 문을 돈다.
      print(i)   # 반복마다 현재 i 를 프린트 한다.
      i = i + 1  # 다음 반복문으로 넘어가기 전, i 에 1 을 더해준다. 
                 # 이 단계를 생략하면, i가 계속 0 이기 때문에, 무한 반복을 하게 된다.
  
0
  1
  2
  3
  4

실행 결과, 0 에서 4 까지 숫자가 출력된다.

5.5.2. while 을 이용한 합 구하기

이번에는 while 문을 이용해 0 에서 4 까지, 더하는 코드를 작성해보자.

total = 0  # 먼저 합계를 할당할 변수, total 을 만든다.
  
i = 0  # 다음으로 i 에 0 을 할당한다.
  
while i < 5:            # i 가 5 보다 작을 동안,
      total = total + i  # total 에 i 를 더하고, 결과를 total 이라고 저장한다.
      print(i, total)
      i = i + 1
  
0 0
  1 1
  2 3
  3 6
  4 10

실행결과, i 가 커질때마다, i 가 기존 숫자에 더해진다.

total  # total 을 확인해보니,
  
10

0 에서 4 까지 합이 구해졌다.

5.5.3. while in while

while 문 내부에 while 문을 또 사용할 수 있다.

1 단부터 5 단까지, 구구단을 외는 프로그램을 짜보자.

구구단의 왼쪽 숫자를 담당할 a 라는 변수에 1 이라는 시작값을 넣는다. awhile 문 밖에 지정해야하한다.

a = 1
  
while a < 10:  # 9 단까지만 보기 위해, a가 10 보다 작을 동안 와일문을 돌게 한다.
      b = 1         # 구구단의 오른쪽 숫자인 b 라는 변수에 1 이라는 시작값을 넣는다.
      while b < 10:     # b 가 10보다 작을 때,
          print(a * b)  # a 와 b 의 곱을 print 한다.
          b = b + 1     # b 에 1 을 더해준다.
      print('---')      # 작은 while 문이 끝날때마다 구분선을 그어준다.
      a = a + 1         # 작은 while 문을 돈 후, a 에 1 을 더해준다.
  

실행결과, 구구단이 9단까지 출력된다.

5.5.4. while 조건에 True/False 이용하기

while 문의 조건은 숫자가 아니여도 된다. True/False 를 이용해, while 문을 작성해보자.

우선 five 라는 변수에 False 라고 지정을 한다.

그리고 i0 이라고 지정한다.

five = False
  i = 0
  
while not five:  # while 이 참이 아닐 동안,
      print(i)     # i 를 프린트 하고,
      i = i + 1    # i 에 1 을 더해준다.
      if i == 5:   # 만약 i 가 5 와 같다면,
          five = True  # five 는 True 라고 지정한다.
  
0
  1
  2
  3
  4

실행결과, 0 에서 4 까지 숫자가 프린트 되었고,

i5 가 되자, five 는 True 가 되어, while 문에서 빠져나왔다.

5.5.5. while True

while 조건을 True 로 놓을 경우, 무한으로 while 루프를 돌 수 있다.

while 문이 도는 동안, hi 를 프린트 하는 코드를 실행시켜보자.

while True:
      print('hi')
  
hi
  hi
  hi
  hi
  ...

그 결과, 주피터 노트북에서 무한으로 'hi' 를 프린트하게 된다.

무한 루프를 강제로 끊기 위해, 커맨드 창에서 ctrl + c, 를 누르거나, 상단에 멈춤 표시를 눌러 강제로 루프를 빠져나와야 한다.

따라서 while 문을 사용할 때, 무한루프에 빠지지 않도록 조심해야한다.

5.6. 루프 제어문

5.6.1. break

break 문은 반복문에서 중간에 강제로 빠져나올 때 사용한다.

for

for 문에서 break 를 사용해 반복문을 빠져나오도록 하자.

0 에서 99 까지 도는 for 문을 생성한다면 for 문이 100번 돌아야한다.

하지만, 중간에 i5 일 때, break 를 넣어주자.

for i in range(100):
      print(i)
      if i == 5:
          break
  

그 결과, i5 가 되자 중간에 for 문을 멈추고 빠져나온다.

0
  1
  2
  3
  4
  5

while

while 에서도 break 를 사용할 수 있다.

i = 0  # i 는 0 이라고 지정한 후 ,

  while True:  # 무한 루프
      print(i)  # i 마다 프린트를 한다.
      if i == 5:  # 만약, i 가 5 와 같다면,
          break   # break 를 실행시킨다.
      i = i + 1   # 한 루프마다 i 를 1 씩 증가시킨다.
  

그 결과, i5 가 되자 while 문을 멈추고, 빠져나왔다.

0
  1
  2
  3
  4
  5

5.6.2. continue

continue 문은 반복을 넘어갈때 사용한다.

for

for 문에서 continue 를 사용해 반복문을 빠져나오도록 하자.

0 에서 9 까지 도는 for 문을 돈 후, 숫자에 100 을 곱해 출력하되,

짝수인 경우 continue 를 사용해보자.

for i in range(10):
      if i % 2 == 0:
          continue
      a = i * 100
      print(a)
  

실행결과, 짝수인 경우 continue 이하 코드가 실행되지 않고 다음 숫자로 넘어갔다.

100
  300
  500
  700
  900

while

while 에서도 continue 를 사용할 수 있다.

i = 0  # i 는 0 이라고 지정한 후 ,

  while i < 10:  # 9 까지의 수
      i = i + 1   # 한 루프마다 i 를 1 씩 증가시킨다.
      if i % 2 == 0:  # 만약, i 가 짝수라면,
          continue   # continue 를 실행시킨다.
      a = i * 100
      print(a)
  

실행결과, 짝수인 경우 continue 이하 코드가 실행되지 않고 다음 숫자로 넘어갔다.

100
  300
  500
  700
  900

5.6.3. pass

pass 문은 어떠한 실행도 하지 않고 넘어가고 싶을 때, 문법을 채우기 위해 사용한다.

for

continue 에서 사용했던 for 문에 pass 문을 넣어보자. 0에서 9까지 숫자 중 짝수를 만나면 pass 한 후, 숫자에 100을 곱해 출력해보자.

for i in range(10):
      if i % 2 == 0:
          pass
      a = i * 100
      print(a)
  

for 문에서 continue 를 사용했을 경우 홀수만 출력되었지만 pass 를 사용하니 모든 숫자가 출력되었다. 즉, pass 는 코드에 영향은 없지만 문법을 채우고 싶을때 사용할 수 있다.

0
  100
  200
  300
  400
  500
  600
  700
  800
  900

함수

pass 문은 함수에 구체적인 코드는 짜지 않고 일단 실행보고 싶을때 손쉽게사용할 수 있다.

def some_func():
      pass
  

6. 파일

6.1. 파일 다루기

6.1.1. 파일 작성하기

파이썬에서 파일을 생성할 수 있다.

예시로, sample.txt 파일을 만들어보자.

f = open('sample.txt', 'w')  # 'sample.txt' 파일을 'w'(작성) 모드로 열기
  
f.close()  # 파일 닫기
  

폴더를 확인해보니, 빈 파일이 생겼다.

6.1.2. Open 설명

방금 작성한 코드를 조금 더 자세히 살펴보자.

open 함수는 파일을 열고 닫을 때 사용하는 함수다.

open 안에, 다루고자 하는 파일 이름을 확장자 까지 적어준 후 (sample.txt), 그 옆에는 열기 모드를 적는다.

파일을 새로 작성하고 싶을 때, write의 약자인, w 모드로 파일을 연다.

f = open('sample.txt', 'w')  # write 모드
  

같은 이름으로 존재하는 파일을 w 로 불러온다면, 기존 자료가 덮어쓰이니 주의해야 한다.

open으로 파일을 열었으면 파일을 닫아야 한다.

f.close()  # 파일 닫기
  

6.1.3. 파일 읽기

이미 존재하는 파일을 불러와, 읽기만 하고싶을 때는, readr 모드로 연다.

f = open('sample.txt', 'r')  # read 모드
  

파이썬3는 파일을 저장할 때, 텍스트 모드 혹은 바이너리 모드로 저장한다.

w 가 아닌 wb 로 작성하면, byte로 파일이 작성된다.

f = open('sample_binary.txt', 'wb')  # write binary 모드
  

텍스트가 아닌 byte파일을 열때는, r 이 아닌 rb 로 열어야 한다.

f = open('sample_binary.txt', 'rb')  # read binary 모드
  

6.2. 파일 내용 다루기

6.2.1. 파일 내용 작성하기

파일에 내용을 작성하고 싶을때는 .write() 메써드를 사용한다.

f = open('sample.txt', 'w')  # write 모드
  
f.write('Hello.\n')
  f.write('Nice to meet you.\n')
  f.write('This is another line.\n')
  

작성 후 파일을 닫는다.

f.close()
  

실행 폴더에서 파일을 확인한다.

내용이 들어와있다.

6.2.2. 파일 내용 읽기

작성한 파일을 불러보자.

f = open('sample.txt', 'r')  # read 모드
  

.read() 메써드를 사용해 내용을 프린트 해보자.

print(f.read())
  
Hello.
  My name is Mind Scale.
  This is another line.

내용이 불려왔다.

.readlines() 메써드를 사용하면, 줄바꿈 단위로 문장이 쪼개져서 불려온다.

f = open('sample.txt', 'r')
  print(f.readlines())
  
['Hello.\n', 'My name is Mind Scale.\n', 'This is another line.\n']

for 문을 사용해, 내용에 접근할 수 있다.

f = open('sample.txt', 'r')

  for line in f:
      print(line)
      print('----')  # 구분선
  
Hello.

  ----
  My name is Mind Scale.

  ----
  This is another line.

  ----

with

파일을 불러준 후, 항상 .close()로 닫아줘야 한다.

매번 .close()를 하기에 번거롭기 때문에, 수행 후에 자동으로 파일을 닫아주는 with를 같이 사용하는 것을 추천한다.

with open('sample.txt', 'r') as f:
      for line in f:
          print(line)
  
Hello.

  My name is Mind Scale.

  This is another line.

6.2.3. encoding

한국어 텍스트를 다룰때 인코딩 문제가 자주 발생한다.

윈도우즈 운영체제의 기본 인코딩 방식은 CP949 이지만,

리눅스나 macOS 에서는 기본 인코딩 방식이 UTF-8 이다.

따라서 윈도우즈에서 한글로 작성한 파일이 간혹 다른 운영체제에서 깨져서 나타날 수 있다.

통상적으로 기본 인코딩을 UTF-8 으로 통일한다.

따라서 자료를 작성할때는 UTF-8 로 저장하고, 열때는 인코딩을 지정하는 것을 잊지말자.

with open('sample_korean.txt', 'w', encoding='utf8') as f:  # 인코딩 지정
      f.write('안녕.\n')
  
with open('sample_korean.txt', 'r', encoding='utf8') as f:  # 인코딩 지정
      print(f.read())
  
안녕.

만약, UTF-8으로 인코딩 된 파일을 CP949로 열려고 한다면, UnicodeDecodeError가 발생한다.

with open('sample_korean.txt', 'r', encoding='cp949') as f:
      print(f.read())
  
UnicodeDecodeError                        Traceback (most recent call last)
  <ipython-input-25-6657879b15e9> in <module>()
        1 with open('sample.txt', 'r', encoding='cp949') as f:
  ----> 2     for line in f:
        3         print(line)

  UnicodeDecodeError: 'cp949' codec can't decode byte 0xec in position 0: illegal...

6.2.4. append mode

이미 존재하는 파일에, 내용을 추가하고 싶다면 append('a') 모드를 사용한다.

with open('sample_korean.txt', 'a', encoding='utf8') as f:  # append 모드
      f.write('This line is added.')  # 추가할 내용 작성
  

파일을 불러와 추가 된 문장을 확인해보자.

with open('sample_korean.txt', 'r', encoding='utf8') as f:  # read 모드
      print(f.read())
  
안녕.
  This line is added.

6.3. os

os 라이브러리는 운영체제 관련 기능을 수행할 수 있다. os 는 operating system 의 약자이다.

import os
  

6.3.1. 현재 작업 디렉토리

현재 작업 디렉토리를 알고 싶다면 getcwd() 메써드를 사용하면 된다. 윈도에서는 디렉토리를 '폴더(folder)'라고 부른다.

os.getcwd()
  
'C:\\Users\\user\\Documents'

현재 자신의 작업 위치가 나온다.

6.3.2. 목록 보기

현재 작업 디렉토리의 있는 파일과 디렉토리 목록을 보고싶다면, listdir() 함수를 사용한다.

os.listdir()
  
['.ipynb_checkpoints',
   '.RData',
   '.Rhistory',
   '46E8.tmp',
   '7895.tmp',
   ...]

현재 경로에 존재하는 파일과 디렉토리가 리스트 형태로 출력된다.

만약, 다른 경로에 존재하는 파일과 디렉토리 목록이 알고 싶다면, listdir()에 경로를 넘겨주면 된다.

os.listdir('C:/Users/user/Anaconda3')    # Anaconda3 디렉토리에 들어있는 파일 목록을 확인
  
['.nonadmin',
   'api-ms-win-core-console-l1-1-0.dll',
   'api-ms-win-core-datetime-l1-1-0.dll',
   'api-ms-win-core-debug-l1-1-0.dll',
   'api-ms-win-core-errorhandling-l1-1-0.dll',
   'api-ms-win-core-file-l1-1-0.dll',
   ...]

6.3.3. 파일

파일명 변경

rename()을 사용해 파일 이름을 변경할 수 있다.

os.rename() 에 현재 파일 이름과 새 파일 이름을 넘겨주면 이름이 변경이 된다. 먼저 '연습.txt'라는 빈 파일을 만들어 보자.

f = open('연습.txt', 'w')
  f.close()
  

os.listdir()로 잘 생성되었는지 확인한다.

os.listdir()
  
['연습.txt']

이제 "연습"을 "os연습"이라고 변경한다.

os.rename('연습.txt', 'os연습.txt')
  
os.listdir()
  
['os연습.txt']

이제 "연습.txt"가 "os연습.txt"로 변경되었다.

파일 삭제

remove()를 통해 파일을 삭제할 수 있다. 앞서 만든 OS연습.txt를 삭제해보자.

os.remove('OS연습.txt')
  
os.listdir()
  
[]

6.3.4. 디렉토리

디렉토리 만들기

osmkdir()를 통해 디렉토리를 생성할 수 있다.

생성하고자 하는 디렉토리 이름을 os.mkdir()에 넘겨주면 된다. os.mkdir()를 통해 '파이썬'이라는 디렉토리를 생성해보자.

os.mkdir('파이썬')    # '파이썬'이라는 디렉토리 생성
  

os.listdir()를 통해 디렉토리가 잘 생성되었는지 확인해보자.

os.listdir()   # 현재 작업 위치에서의 파일 목록들
  
['파이썬']

디렉토리 바꾸기

현재 작업디렉토리를 변경하고자 한다면, chdir()를 사용하면 된다.

이는 불러오고 싶은 파일이 다른 경로에 있거나, 저장하고 싶은 디렉토리가 현재 작업 디렉토리와 다른 경우에 사용된다.

os.chdir에 변경하고 싶은 디렉토리를 넘겨주면 된다.

os.chdir(<변경하고 싶은 작업 디렉토리>)
  
상대 경로로 이동
os.getcwd()
  
'C:\\Users\\user\\Documents'

현재의 작업디렉토리는 user/Documents 라는 디렉토리이다.

아래 코드를 실행시키고, 다시 현재 작업디렉토리를 확인하자.

os.chdir('../')
  
os.getcwd()
  
'C:\\Users\\user'

..은 현재 디렉토리의 상위 디렉토리를 의미한다. 작업디렉토리가 이전과 같이 Documents 가 아니라 상위디렉토리인 user 로 변경된 것을 확인할 수 있다.

'../' 를 사용하면 상위 디렉토리 내에 있는 디렉토리나 파일로 접근이 가능하다.

os.chdir('../파이썬')    # 현재의 상위디렉토리에 있는 '파이썬' 이라는 디렉토리로 작업디렉토리 변경
  
절대 경로로 이동

위에서 생성한 파이썬이라는 파일로 디렉토리를 변경해보자.

os.chdir('C:/Users/user/Documents/파이썬')  # 파이썬이라는 디렉토리로 작업위치 변경
  

변경 되었는지 확인하기 위해, os.getcwd()를 사용하자.

os.getcwd()
  
'C:\\Users\\user\\Documents\\파이썬'

현재 작업 디렉토리가 파이썬 이라는 디렉토리로 변경 되었다.

디렉토리 삭제

파일이 아닌 디렉토리를 삭제하고 싶을 경우, rmdir()를 사용한다. 전에 만들어놓은 "파이썬" 디렉토리를 삭제해보도록 하자.

os.rmdir('파이썬')
  
os.listdir()
  
[]

현재 디렉토리를 확인하면 디렉토리가 삭제된 것을 알 수 있다.

6.3.5. 추가 정보 확인

os의 하위 모듈인 os.path에는 파일과 디렉토리의 여러 가지 추가 정보를 확인할 수 있는 함수들이 있다.

존재 확인

os.path.exists()로 파일이나 디렉토리의 존재 유무를 알 수 있다.

현재 디렉토리에 "python" 라는 디렉토리가 있는지 확인한다.

os.path.exists('python')
  
False

파일인가 디렉토리인가

사용자가 입력한 경로가 파일인지 디렉토리인지 궁금할땐, .isdir() 혹은 .isfile()을 사용하면 된다.

os.mkdir('python')
  

먼저 python이라는 디렉토리를 만들고 'python'이라는 경로가 디렉토리인지 확인한다.

os.path.isdir('python')
  
True

python 은 디렉토리가 맞다는 결과가 출력이 되었다.

이번엔 is.file() 에 "python"을 넘겨주자.

os.path.isfile('python')
  
False

False 라는 결과가 나온다.

수정된 시간

os.path.getmtime() 을 사용하면, 파일의 수정된 시간 등을 구할 수 있다.

os.path.getmtime('sales.csv')
  
1516758099.6377094

결과는 유닉스 시간(UNIX time)이라는 방식으로 표현된다. 일반적인 시간 단위는 60초 = 1분, 24시간 = 1일 등으로 십진법을 따르지 않기 때문에 계산이 불편하다. 유닉스 시간은 모든 시간을 초단위로만 나타낸다. 유닉스 시간으로 0초는 1970-01-01 00:00:00이다. 위의 예에서 sales.csv가 생성된 시간은 1516758099초로 2018년 1월 24일에 해당한다. 시간을 다루는 구체적인 방법은 다음에 알아보도록 하자.

6.3.6. 연습문제

현재 작업 폴더에서 확장자가 .txt인 모든 파일의 내용을 출력하는 프로그램을 작성하라.

7. 유용한 라이브러리

7.1. 날짜

크롤링을 할 때, 날짜를 기준으로 자료를 가져와야 하는 경우들이 있다.

이번 강의에서는 파이썬에서 날짜를 쉽게 처리하는 방법을 살펴보도록 하자.

이번 강의에서는 몇 가지 기능에 대해서만 알아본다.

더 많은 기능들은 공식문서에서 확인할 수 있다.

http://arrow.readthedocs.io/en/latest/

7.1.1. 설치

날짜를 처리할 땐 arrow 라는 라이브러리를 사용한다.

pip install arrow 를 통해 설치를 할 수 있다.

import arrow
  

7.1.2. now

arrow.now()를 이용하여 현재시간을 알 수 있다.

arrow.now()
  
<Arrow [2018-01-04T18:10:27.713067+09:00]>

현재 시간은 2018년 1월 4일 오후 6시 10분이 나온다.

다른 장소의 현지 시간을 구할수도 있다.

arrow.now('US/Pacific')   # 미태평양시간의 현재시간을 구한다.
  
<Arrow [2018-01-04T10:10:27-08:00]>
arrow.utcnow()    # 협정 세계시인 utc 기준으로, 현재 시간을 구한다.
  
<Arrow [2018-01-04T09:10:53.289173+00:00]>

그렇다면 utc 시간으로 현재 시간이 나온다.

7.1.3. get

날짜를 arrow형태로 받고 싶다면, arrow.get()을 사용하면 된다.

arrow 형태로 날짜를 받으면, 날짜를 다룰 수 있다.

arrow.get('2017-12-25')   # 2017년 12월 25일을 arrow 형태로 가져온다.
  
<Arrow [2017-12-25T00:00:00+00:00]>

시간 단위까지 넣어주고 싶다면 날짜 뒤에 넘겨주면 된다.

date = arrow.get('2017-12-25 16:23')    # 17년 12월 25일 오후 4시 23분을 넘겨준다.
  
date
  
<Arrow [2017-12-25T16:23:00+00:00]>

원하는 날짜가 arrow 형식으로 저장된 것을 확인할 수 있다.

문자열을 사용하지 않고도, 날짜를 지정할 수 있다.

date = arrow.get(2017, 12, 25)   # 2017년 12월 25일을 arrow 형태로 가져온다.
  date
  
<Arrow [2017-12-25T00:00:00+00:00]>

위와 같은 결과를 얻을 수 있다.

날짜가 어떻게 구성되어있는지 직접 지정할 수도 있다.

arrow.get('20171225', 'YYYYMMDD')  # 연도 네 자리, 월 두자리, 일 두자리로 인식하게 지정
  
<Arrow [2017-12-25T00:00:00+00:00]>

7.1.4. format

.format()을 사용하여 날짜의 출력 형식을 지정할 수 있다.

예를 들어, 날짜를 '년/월' 형식으로 나타내고 싶다면 format에 'YY/MM'을 넘겨준다.

date.format('YY/MM')
  
'17/12'

Y 는 연도,

M 는 월,

D 는 일,

h 는 시,

m 은 분을 뜻한다.

date.format('YYYY.MM')
  
'2017.12'

M 4개를 사용하면 숫자 대신 영어로 월이 출력된다.

date.format('YYYY.MMMM')
  
'2017.December'
date.format('YY년 MM월 DD일')
  
'17년 12월 25일'

7.1.5. to

현재 시간이 아닌 특정 시간을 다른 곳의 시간대로 변환하고 싶다면 .to() 사용하면 된다.

예를 들어, 한국의 2017년 12월 25일 16시 43분이 미태평양에서 언제인지 알아보자.

date = arrow.get('2017-12-25 16:23')  # 우선 2017/12/25 16시 43분을 arrow 형태로 받아옴.
  
date.to('US/Pacific')   # 시간을 미태평양 시간으로 받는다.
  
<Arrow [2017-12-25T08:23:00-08:00]>

미 태평양은 한국이 16시 일 때, 오전 8시인 것을 알 수 있다.

7.1.6. shift / replace

.shift()이용해, 해당 날짜를 다른 날짜로 옮길 수도 있다.

three_weeks_later = date.shift(weeks=3)   # date 를 3 주후의 시간으로 옮긴다.
  
<Arrow [2018-01-15T16:23:00+00:00]>
three_weeks_before = date.replace(weeks=-3)   # date 를 3주 전의 시간으로 옮긴다.
  
<Arrow [2017-12-04T16:23:00+00:00]>

12월 25일의 3주 전인 12월 4일로 지정되었다.

주 단위 외에도 년, 일, 시간 단위 까지 이동을 할 수 있다.

date.replace(days=3)   # date 를 3일 후의 시간으로 옮긴다.
  
<Arrow [2017-12-28T16:23:00+00:00]>

날짜가 28일로 수정되었다.

'days' 가 아니라 'day'를 옵션에 넘겨주면 해당 일로 지정할 수 있다.

date.replace(day=3)   # date 의 날짜를 3일로 옮긴다.
  
<Arrow [2017-12-03T16:23:00+00:00]>

그럼 date 가 12월 3 일인 날짜로 변경되었다.

7.1.7. weekday

.weekday()를 통해 해당 날짜가 무슨 요일이였는지도 알 수 있다.

date.weekday()   # date 변수의 날짜가 무슨 요일이였는지 알아보자.
  
0

date 는 12월 3일의 결과가 '0'으로 나온다.

'0' 이 월요일이고, '6' 은 일요이다.

7.1.8. 날짜 빼기

arrow에서 - 사용해 날짜에서 날짜를 뺄 수 있다.

date1 = arrow.get('2017-11-12')   # date1 을 11월 12일로 지정한다.
  date2 = arrow.get('2017-12-01')   # date2 를 12월 1일로 지정한다.
  
difference = date2 - date1    # date2 에서 date1 을 빼준다.
  

.days() 를 통해 며칠인지 확인한다.

difference.days   # difference.days 로 총 며칠인지 확인한다.
  
19

'date2' 와 'date1'의 차이가 19일인 것을 알 수 있다.

7.2. 이미지 처리

Pillow 라는 라이브러리는 이미지 처리할 때 사용되는 유용한 라이브러리이다.

공식문서 - (https://pillow.readthedocs.io/)

7.2.1. 이미지 파일 찾기

from PIL import Image
  import os
  

7.2.2. 첫번째 이미지 선택

작업 폴더에 마음에 드는 사진을 넣어둔다.

image_path = 'images/gecko.jpg'
  

PillowImage를 통해 사진을 연다.

image = Image.open(image_path)
  

7.2.3. 그림 보기

image
  
<PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=275x183 at 0x2429BD2DC18>

7.2.4. 크기 변환

image.resize((128, 128))  # 픽셀 단위
  
<PIL.Image.Image image mode=RGB size=128x128 at 0x2429B1C1400>
resize = image.resize((128, 128))
  

7.2.5. 저장

resize.save('images/resized.jpg')
  

7.2.6. 회전

resize.rotate(45)  # 반시계방향 각도
  
<PIL.Image.Image image mode=RGB size=128x128 at 0x2429BDF6FD0>

7.2.7. 반전

상하반전

resize.transpose(Image.FLIP_TOP_BOTTOM)
  
<PIL.Image.Image image mode=RGB size=128x128 at 0x2429BDFE080>

좌우 반전

resize.transpose(Image.FLIP_LEFT_RIGHT)
  
<PIL.Image.Image image mode=RGB size=128x128 at 0x1CD76DE1B00>

90도로 돌리기

resize.transpose(Image.ROTATE_90)
  
<PIL.Image.Image image mode=RGB size=128x128 at 0x2429BD2DCC0>

7.2.8. 이미지 필터

PillowImageFilter를 통해 사진에 필터링을 할 수 있다.

from PIL import ImageFilter
  

블러 처리

resize.filter(ImageFilter.BLUR)
  
<PIL.Image.Image image mode=RGB size=128x128 at 0x1CD76DF3EB8>

엠보싱

resize.filter(ImageFilter.EMBOSS)
  
<PIL.Image.Image image mode=RGB size=128x128 at 0x1CD76DF3FD0>

윤곽

resize.filter(ImageFilter.CONTOUR)
  
<PIL.Image.Image image mode=RGB size=128x128 at 0x1CD76DFC0F0>

자세히

resize.filter(ImageFilter.DETAIL)
  
<PIL.Image.Image image mode=RGB size=128x128 at 0x1CD76DFC1D0>

날카롭게

resize.filter(ImageFilter.EDGE_ENHANCE)
  
<PIL.Image.Image image mode=RGB size=128x128 at 0x1CD76DFC358>

부드럽게

resize.filter(ImageFilter.SMOOTH)
  
<PIL.Image.Image image mode=RGB size=128x128 at 0x1CD76DFC198>

7.2.9. Convert

.convert()에 색상 표현 방식을 넘겨줘서 이미지를 원하는 색상표현으로 변경할 수 있다.

자주 사용되는 방식들은 다음과 같다

이미지에 .convert() 함수에 L, RGB, RGBA 등을 넘겨줘서 원하는 색상표현 방식으로 변환할 수 있다.

7.2.10. 흑백

.convert()에 'L'을 넘겨줘서 8 비트 그레이스케일로 변환할 수 있다.

resize.convert('L')  # ITU-R 601-2 luma transform
  
<PIL.Image.Image image mode=L size=128x128 at 0x1CD76DFC400>

7.2.11. 행렬로 변환

행렬로 변환하는 것은 numpy.asarray를 사용한다.

import numpy
  
m = numpy.asarray(resize)
  

형태를 보면 가로 128, 세로 128인 행렬 3개가 겹쳐진 형태가 된다.

m.shape
  
(128, 128, 3)

흑백 이미지를 행렬로 바꿔보자.

bw = numpy.asarray(resize.convert('L'))
  

가로 128, 세로 128인 행렬 1개가 된다.

bw.shape
  
(128, 128)

7.2.12. draw text

사진에 텍스트를 한번 넣어보자.

from PIL import ImageDraw, ImageFont
  

우선 텍스트를 넣을 사진을 불러온다.

base = Image.open(os.path.join(os.getcwd(), 'images', images[1])).convert('RGBA')
  
# 흰색-투명으로 이미지와 같은 크기 이미지 하나 더 만들기
  txt = Image.new('RGBA', base.size, (255,255,255,0))
  d = ImageDraw.Draw(txt)


  d.text((10,10), "Hello", fill=(255,255,255,128))  # 위치, 텍스트, 흰색-반투명

  d.text((10,60), "World", fill=(255,255,255,255))  # 위치, 텍스트, 흰색-불투명

  Image.alpha_composite(base, txt)
  
<PIL.Image.Image image mode=RGBA size=128x128 at 0x1CD76E039B0>

RGBA 형태의 그림은 .png 파일로 저장해야한다. .jpg 파일로 저장하면 오류가 발생한다.

7.2.13. 사진 여러 장을 pdf로 합치기

3 개의 이미지 파일을 불러오자.

im1 = Image.open('images/gecko.jpg')
  im2 = Image.open('images/gecko_resized.jpg')
  im3 = Image.open('images/gecko_rotated.jpg')
  
image_list = [im2, im3]
  
im1 = im1.convert('RGB')
  

이미지의 사이즈를 조정하고 싶다면 im.resize(x 크기, y 크기) 로 지정하면 된다.

tiff 파일을 먼저 생성을 해야 pdf 가 생성이 된다.

im1.save('images.tiff', save_all=True, append_images=image_list)
  im1.save('images.pdf', save_all=True, append_images=image_list)
  

후에 PyPDF2 를 사용해 사진을 다른 PDF 페이지 사이에 끼워넣을 수 있다.

8. 프로그램의 구조

8.1. 예외 처리

코드를 짜다보면, 늘상 에러가 난다.

ZeroDivisionError                         Traceback (most recent call last)
  <ipython-input-58-2b706ee9dd8e> in <module>()
  ----> 1 3 / 0

  ZeroDivisionError: division by zero

예시의 에러같은 경우, 3 을 0 으로 나누려고 하니 ZeroDivisionError 가 발생했다.

이번 시간에는 에러가 발생할때, tryexcept 를 사용해, 예외사항을 처리하는 방법을 알아보자.

먼저, 평균을 구하는 함수를 만든다.

def mean(lst):          # 숫자가 들어있는 리스트를 인수로 받는다.
      합계 = sum(lst)     # 썸으로 리스트의 합계를 구하고,
      개수 = len(lst)     # 렌으로 리스트의 개수를 구한다.
      return 합계 / 개수   # 합계를 개수로 나누어 돌려준다.
  
mean([7, 3, 5])
  
5.0

mean 함수에 [7, 3, 5] 를 넘겨주면, 결과로 평균값 5가 나온다.

하지만 빈 리스트를 넘겨주면,

mean([])
  
Traceback (most recent call last)
  <ipython-input-142-e9e2222d800d> in <module>()
  ----> 1 mean([])

  <ipython-input-140-a06ba1720bc6> in mean(lst)
        2     합계 = sum(lst)  # 썸으로 합계를 구하고,
        3     개수 = len(lst)  # 렌으로 개수를 구한다.
  ----> 4     return 합계 / 개수  # 합계를 개수로 나누어 돌려준다.

  ZeroDivisionError: division by zero

개수가 0이 되면서, ZeroDivisionError 가 발생한다.

8.1.1. try except 구문

에러가 발생해도 프로그램이 멈추지 않고 실행될 수 있도록 예외 처리를 할 수 있다.

예외 처리를 할 때에는 try, except 라는 구문을 사용한다.

만약 에러가 난다면, except 밑에 있는 코드가 실행 되고 에러가 발생하지 않는다면 그대로 실행된다.

try:                             # try 후에,
      <에러가   있는 코드>             # 들여쓰기를 하고 실행시킬 코드를 넣는다. 
  except 발생 에러:                     # except 하고 지정한 에러가 일어날 경우, 
      <에러가 발생하면 실행되는 코드>   # 실행할 코드를 입력한다.
  

mean0 으로 나눌때, ZeroDivisionError 가 발생하지 않도록 try, except 구문을 적어보자.

try:
      mean([])
  except ZeroDivisionError:               # zero division error 가 발생할 경우
      print('0 으로 나누려고 합니다')        # '0 으로 나누려고 합니다'를 프린트
  
0 으로 나누려고 합니다

이번에는 에러가 발생하지 않고, 미리 지정해놓은 문구가 출력되었다.

이번에는 정상적인 숫자 리스트를 mean 함수에 넘겨준다.

try:
      print(mean([3, 4]))
  except ZeroDivisionError:
      print('0 으로 나누려고 합니다')
  
3.5

문제없이, 평균값이 나왔다.

8.1.2. 발생 에러 지정

except 뒤에 에러를 지정하여 발생하는 에러에 대해서 예외사항으로 처리할 수 있다.

sum 함수는 문자열을 처리할 수 없기 때문에 TypeError 가 발생한다.

mean('hello')  # mean 함수에 헬로라는 문자열을 넘겨준다.
  
Traceback (most recent call last)
  <ipython-input-146-bfd617a58807> in <module>()
  ----> 1 mean('hello')

  <ipython-input-140-a06ba1720bc6> in mean(lst)
        1 def mean(lst):       # 숫자가 들어있는 리스트를 인수로 받는다.
  ----> 2     합계 = sum(lst)  # 썸으로 합계를 구하고,
        3     개수 = len(lst)  # 렌으로 개수를 구한다.
        4     return 합계 / 개수  # 합계를 개수로 나누어 돌려준다.

  TypeError: unsupported operand type(s) for +: 'int' and 'str'

문자열이 들어올 경우 숫자 리스트를 넣어달라는 문구를 프린트하도록 한다.

try:
      print(mean('hello'))
  except TypeError:                    # 만약 타입에러가 났다면,
      print('숫자 리스트를 넣어주세요')    # '숫자 리스트를 넣어주세요'를 프린트 한다.
  
숫자 리스트를 넣어주세요

8.1.3. 에러 여러개 지정

try 뒤에 여러 except 문을 넣을 수 있다.

try:
      print(mean([]))
  except TypeError:                      # Type Error 일때, 
        print('숫자 리스트를 넣어주세요')    # 프린트할 메세지와,
  except ZeroDivisionError:            # zero division error 일때 
        print('0 으로 나누려고 합니다')    # 프린트할 메세지를 다르게 지정한다.
  
0 으로 나누려고 합니다

이렇게 두 종류의 에러에도 대응할 수 있는, try except 문이 생성되었다.

8.1.4. 에러 종류 미지정

except 를 쓸때, 에러를 지정하지 않을 수 있다.

에러를 지정하지 않으면, 어떠한 에러가 발생해도 같은 처리를 한다.

try:
      print(mean([]))
  except:
      print('문제가 생겼습니다')
  
문제가 생겼습니다

8.1.5. Except 에러 메세지 표시

ZeroDivisionError 를 자세히 살펴보면,

3 / 0
  
ZeroDivisionError                         Traceback (most recent call last)
  <ipython-input-2-2b706ee9dd8e> in <module>()
  ----> 1 3 / 0

  ZeroDivisionError: division by zero

에러 이름 뒤에 division by zero 라는 설명이 나온다.

except 에서 이 설명이 출력되도록 지정할 수 있다.

try:
      mean([])
  except ZeroDivisionError as e:
      print(e)
  
division by zero

ZeroDivisionError 가 발생할 때 나오는 문구인, division by zero 가 프린트 된다.

8.1.6. Else

예외 처리에서, else 구문도 사용할 수 있다.

try / except문에서 에러가 발생하지 않을시, 실행될 블록을 지정할 수 있다.

try:
      <에러가   있는 코드>
  except 발생 에러:
      <에러가 발생하면 실행되는 코드>
  else:        # try 뒤에 에러가 발생하지 않으면 바로 else 뒤의 코드가 실행
      <예외 상황이 발생 안하면 실행되는 코드>
  

예제를 통해, 자세히 알아보자.

tryexcept 구문 뒤에, else 절을 추가한다.

try:
      number = mean([3, 4])
  except:                   # 에러가 발생하면,
      print('에러입니다')    # "에러입니다" 를 출력하고
  else:                      # 에러가 발생하지 않으면,
      print(number * 2)     # 변수 number 에 2를 곱해 출력한다.
  
7

34로 된 리스트를 mean 함수에 넘겨주면, 에러가 발생하지 않으므로 else 절로 넘어가서 print(number*2) 코드가 실행되었다.

8.1.7. raise

특정 에러가 발생했을 때 알려줄 메세지를 raise 로 쉽게 바꿀 수 있다.

raise 뒤에 사용하고 싶은 에러와 출력할 문장을 넣어주면 메시지가 바뀐다.

raise IndexError('하하 그런 것 없어요')
  
IndexError                                Traceback (most recent call last)
  <ipython-input-8-766dc464d4a8> in <module>()
  ----> 1 raise IndexError('하하 그런 것 없어요')

  IndexError: 하하 그런 것 없어요

예시로 인수가 음수일 경우, ValueError 와 함께 '음수입니다' 라는 메시지를 띄우는 함수 fac 를 만들어보자.

def fac(n):
      if n < 0:
          raise ValueError('음수입니다')
  

양수인 숫자 10을 넣어주면 문제없이 코드가 실행된다.

fac(10)
  

하지만 음수를 넣어주면 ValueError 와 미리 지정한 에러 메시지를 띄운다.

fac(-1)
  
ValueError                                Traceback (most recent call last)
  <ipython-input-11-d65c80c453f4> in <module>()
  ----> 1 fac(-1)

  <ipython-input-9-12a49895dd4e> in fac(n)
        1 def fac(n):
        2     if n < 0:
  ----> 3         raise ValueError('음수? 음수? 뤼얼리?')

  ValueError: 음수입니다

8.2. 주요 에러 종류

이번에는 python 에서 자주 발생하는 에러의 종류에 대해 알아보자.

python 에는 많은 에러가 있지만, 이 강의에서는 몇 가지만 보도록 한다.

SyntaxError, NameError, TypeError, ValueError, IndexError ...
  

8.2.1. SyntaxError

SyntaxError 는 파이썬 문법에 어긋나게 코드를 작성한 경우 발생한다.

코드를 문법에 맞게 고치면 해결된다.

몇 가지 SyntaxError 상황을 보자.

1 +/ 1
  
File "<ipython-input-159-23a9bc70ce7f>", line 1
      1 +/ 1
         ^
  SyntaxError: invalid syntax

+/ 라는 연산기호가 없기 때문에 SyntaxError 가 발생한다.

1 + 1
  1 / 1
  
1.0

위와 같이 수정하면 문제가 해결된다.

'안녕하세요
  
File "<ipython-input-160-104019af679f>", line 1
      '안녕하세요
            ^
  SyntaxError: EOL while scanning string literal

따옴표가 짝이 맞지 않아, SyntaxError 가 발생한다.

'안녕하세요'
  
안녕하세요

안녕하세요 뒤에 따옴표를 붙이면 문제가 해결된다.

(1+1
  
File "<ipython-input-161-095bac307b77>", line 1
      (1+1
          ^
  SyntaxError: unexpected EOF while parsing
(1 + 1)
  
2

위와 같은 경우는 닫는 괄호를 붙여주면 해결된다.

8.2.2. NameError

NameError 는 정의되지 않은 변수나 함수 등의 이름을 실행시킬 때 발생한다.

print(abc)
  
Traceback (most recent call last)
  <ipython-input-164-c5a4f3535135> in <module>()
  ----> 1 print(abc)

  NameError: name 'abc' is not defined

abc 라는 변수를 선언하지 않았기 때문에 NameError 가 발생한다.

abc = 3
  print(abc)
  
3

abc 라는 변수를 3으로 지정하면 문제가 해결된다.

SyntaxErrorNameError 는 주로 오타 문제로 비교적으로 쉽게 해결이 가능하다.

다음 에러들은, 다루고 있는 데이터와 관련해 발생하는 에러들이다.

8.2.3. Type Error

TypeError 는 적절하지 않은 자료형 때문에 발생한다.

sum 함수는 합계를 구하는 함수인데, sum 함수에 문자열 리스트를 넘겨주도록 하자.

sum(['one', 'two', 'three'])
  
Traceback (most recent call last)
  <ipython-input-173-f98a3e650ff1> in <module>()
  ----> 1 sum(['one', 'two', 'three'])

  TypeError: unsupported operand type(s) for +: 'int' and 'str'

문자열은 합할 수가 없기 때문에 TypeError 가 발생한다.

sum([1, 2, 3])
  
6

반면, 숫자를 넘겨주면 TypeError 가 발생하지 않고 해결된다.

8.2.4. Value Error

ValueError 는 자료형은 맞지만 값이 틀린 경우이다.

예를 들어, 인티저 함수는 문자열을 받을 수 있다.

하지만 숫자가 아닌, 알파벳 'a' 를 인티저로 바꿀 수는 없다.

int('a')
  
Traceback (most recent call last)
  <ipython-input-131-b3c3f4515dd4> in <module>()
  ----> 1 int('a')

  ValueError: invalid literal for int() with base 10: 'a'

따라서, ValueError 가 발생한다.

int 에는 값이 숫자인 문자열을 넘겨줘야 에러가 발생하지 않는다.

int('3')
  
3

8.2.5. Index Error

IndexError 는 선택한 인덱스가 주어진 범위를 벗어나면 발생한다.

alphabet = ['a', 'b']
  alphabet[5]       # alphabet 이라는 리스트에서 6번째 값을 선택한다.
  
Traceback (most recent call last)
  <ipython-input-139-b6a934feab86> in <module>()
  ----> 1 alphabet[5]

  IndexError: list index out of range

6 번째 값은 alphabet 리스트의 범위를 벗어났기 때문에 IndexError가 발생한다.

주어진 범위를 넘어가지 않는 인덱스를 선택해야 에러가 발생하지 않는다.

alphabet[1]
  
'b'

8.2.6. Key Error

사전에 존재하지 않는 key 를 불러올때 KeyError 가 발생한다.

fruit = {'사과': '100원', '바나나': '200원', '포도': '150원'}
  

fruit 이라는 사전에 존재하지 않는 key 인 망고를 불러오면 KeyError 가 발생한다.

fruit['망고']
  
Traceback (most recent call last)
  <ipython-input-170-b397f48a5f8c> in <module>()
  ----> 1 fruit['망고']

  KeyError: '망고'

KeyError 를 피하기 위해 망고를 부를 때 get 을 사용하면 된다.

fruit.get('망고', '300원')    # 만약, 망고가 사전에 없다면 삼백원이라는 값을 돌려주도록 한다.
  
'300원'

KeyError 가 발생하지 않고 해결된 것을 확인할 수 있다.

이외에도 다양한 에러가 발생할 수 있지만, 이 강의에서는 자주 일어나는 에러 몇 가지만 다루었다.

8.3. 함수

함수(function)는 특정한 작업을 하는 코드 조각이다. Python에서 함수를 부를 때는 항상 뒤에 괄호(())를 붙여준다.

함수에는 어떤 값들을 넘겨 줄 수 있는데 이를 인자(argument)라고 한다. 인자는 괄호 안에 순서대로 쓴다. 예를 들어, 파이썬 내장 함수인 round 에 인자로 3.5를 넘겨주면,

round(3.5)
  
4

반올림 되어서 4가 출력된다.

8.3.1. 함수 정의

함수를 정의할 때는 def 라는 키워드를 사용한다. def 는 정의를 뜻하는 definition 의 약자이다.

'안녕' 이라는 문자열을 반환하는 hello 라는 함수를 만들어보자.

def hello():
      return '안녕'
  

함수를 실행해보면 '안녕'이라는 결과가 반환된다.

hello()
  
안녕

함수의 실행 결과는 변수에 저장할 수 있다.

a = hello()
  a
  
안녕

인자

이번에는 이름을 넘겨 받아 인사에 포함시키는 hello_name라는 함수를 만들어보자.

def hello_name(name):
      return '안녕 ' + name
  

이제 hello_name을 호출 할 때 괄호 안에 값을 인자로 넘겨주면, 그 이름은 함수 안에서 name으로 사용된다.

hello_name('헤이즐')
  
안녕 헤이즐

만약, 함수에 인자를 넘겨주지 않으면

hello_name()
  
TypeError                                 Traceback (most recent call last)
  <ipython-input-9-ec6643fba34e> in <module>()
  ----> 1 hello_name()

  TypeError: hello_name() missing 1 required positional argument: 'name'

name 이라는 인자를 받지 못했다고 TypeError 를 낸다.

인자가 여러 개인 함수

인자를 여러 개 넣을 수 있다.

def hello_punc(name, punc):
      return '안녕 ' + name + punc
  
hello_punc('헤이즐', '!')
  
안녕 헤이즐!

인자의 기본값

함수의 인자가 여러 가지일 때 매번 모든 인자를 넣어주기 번거로울 수 있다. 이때 인자의 기본값을 지정할 수 있다. 아래의 예에서는 punc의 기본값을 느낌표로 지정한다.

def hello_punc(name, punc='!'):
      return '안녕 ' + name + punc
  

이제 hello_punc를 호출 할 때 punc를 넘겨주지 않으면 기본값인 느낌표를 사용한다.

hello_punc('헤이즐')
  
안녕 헤이즐!

인자를 넘겨주면, 넘겨준 인자를 사용한다.

hello_punc('헤이즐', '?')
  
안녕 헤이즐?

반환값이 없는 함수

아래 함수는 '안녕'이라고 출력하고 아무 것도 반환하지 않는다.

def print_hello():
      print('안녕')
  
x = print_hello()
  
안녕

x에는 아무 것도 들어있지 않다.

x
  

x에는 없음을 뜻하는 None이 들어있다.

x == None
  
True

여러 값 반환하기

여러 개의 값을 반환할 때는 콤마로 구분한다. 다음 함수는 반지름을 인자로 넘겨 받아 원의 둘레와 넓이를 계산하여 반환한다.

import math

  def circle(r):
      둘레 = 2 * math.pi * r
      면적 = math.pi * r ** 2
      return 둘레, 면적
  

circle 함수를 호출한다.

circle(1)
  
6.283185307179586, 3.141592653589793

두 개의 값이 튜플로 반환된다. 만약 변수에 할당하고 싶으면 아래와 같은 형태를 사용할 수 있다.

c, a = circle(1)
  

유효 범위

함수 밖에서 정의된 변수는 함수 안쪽에 사용할 수 있다. 하지만 함수 안쪽에서 정의된 함수는 함수 바깥쪽에서 사용될 수 없다.

a = 1
  

add 함수를 생성한다.

def add(x):
      b = x + 2
      return x + a
  
add(3)
  
4

결과로 4 이라는 값이 출력된다. 함수 안에서는 바깥쪽에서 정의된 a를 부를 수 있기 때문이다.

함수 안에서 정의된 b를 바깥쪽에서 불러보면

b
  
NameError: name 'b' is not defined

NameError 가 발생한다.

8.4. 모듈

Python 파일은 .py 확장자를 붙인다. 하나의 파이썬 파일은 "모듈"이라고 부른다. 다음과 같은 파일을 작성해서 magic.py로 저장하자.

var = 1

  def add_one(x):
      return x + 1
  

8.4.1. import

모듈은 import 문으로 불러서 사용할 수 있다.

import magic
  

import한 모듈 이름 뒤에 .을 붙이면 모듈의 변수나 함수에 접근할 수 있다.

magic.add_one(1)
  
2

8.4.2. from .. import ..

fromimport를 사용하면 모듈의 변수나 함수를 직접 불러올 수 있다.

from magic import add_one
  
add_one(1)
  
2

8.4.3. 모듈 실행

명령창에서 모듈을 직접 실행할 수도 있다.

python magic.py

8.4.4. 파이썬 에디터

.py 라는 확장자로 끝나는 파일은 파이썬 에디터로 열고 작업할 수 있다.

다양한 파이썬 에디터가 있지만, 이번 시간에는 AtomPyCharm 에 대한 소개를 하겠다.

Atom

Atom 은 간단하게, 파이썬 파일을 작성하거나 편집할 수 있는 에디터이다.

설치

Atom 웹사이트에 접속한다 (https://atom.io/).

다운로드 버튼을 눌러 설치파일을 받아 실행시킨다.

실행

Atom 을 실행시킨다.

Atom 이 실행된다.

작업하기

.py 파일을 불러오고 싶을때는, File - Open 을 들어가 파일을 불러온다.

.py 파일이 불려왔다.

Atom 에서 작성 및 편집 작업을 거칠 수 있다.

PyCharm

Atom 에는 많은 기능이 제한되어 있다.

복잡한 파이썬 작업을 해야할 경우, PyCharm 과 같이 많은 기능을 제공하는 에디터가 필요하다.

설치

PyCharm 다운로드 사이트에 접속한다. (https://www.jetbrains.com/pycharm/download)

기능이 더욱 많은 전문가 버전을 사용할 수 있지만, 일단 무료 버전인 Community 버전을 설치한다.

실행

PyCharm 을 실행한다.

File - Open 으로 .py 파일을 연다.

.py 파일이 불려왔다.

작업하기
project 단위로 작업하기

PyCharm 에서는 project 단위로 작업을 진행하는 것이 수월하다.

File - New Project 로 새로운 프로젝트를 만든다.

Location 에서 프로젝트 명을 변경하고,

Interpreter 에서 Create Conda Env 를 클릭한다. Interpreter 는 사용할 파이썬을 의미한다.

새로운 Conda Environment 의 이름을 만든 후, 사용할 Python versionLocation 을 확인한다.

OK 를 누른 후 Create 를 누르면, 환경이 만들어진다.

다시 .py 파일을 열어보자.

.py 의 실행 결과를 확인하고 싶다면, 우측 상단의 Run 버튼 을 클릭한다. 아래 창에서 파일의 실행 결과를 볼 수 있다.

주피터 노트북

PyCharm 에서 .ipynb 파일을 열수도 있다.

파일을 실행시키기고 싶다면, 먼저 주피터 노트북을 띄우듯 코맨드 창에서 주피터 노트북 커널을 띄워, token 이 있는 URL 을 복사한다.

다음, PyCharm 에서 Run 버튼 을 누른다.

주소 입력창에 복사한 URL을 붙여넣는다.

이제, 주피터 노트북과 같이 사용할 수 있다.

8.4.5. pyinstaller

pyinstaller 를 통해 파이썬 파일을 실행 파일인 .exe 형태로 저장할 수 있다.

pip install pyinstaller 를 통해서 설치할 수 있다.

pip install pyinstaller

GUI 파일 생성

.exe 파일로 변경할 파이썬 파일을 우선 생성하자.

아주 간단한 GUI (Graphical User Interface) 파일을 생성하자.

tkinter 라는 패키지를 불러온다.

import tkinter as tk
  

tkinter 에서 Tk를 이용하여 새로운 GUI 를 생성할 수 있다.

root = tk.Tk()    # 메인 작업이 저장된다
  root.title('Testing !')  # GUI 제목을 설정한다
  root.mainloop()   # 저장한 파일을 실행한다
  

위의 코드를 jupyter notebook 에서 실행시키면

제목만 Testing ! 으로 설정된 별도의 작은 GUI 창이 뜬다.

해당 파일을 .exe 파일로 생성하여 클릭만으로 GUI 창을 실행시켜보자.

변환

위에서 사용된 코드를 메모장에 붙여넣은 후에 gui.py 파일로 저장한다.

파이썬 파일로 저장된 것을 확인할 수 있다.

gui.py 를 클릭하여도 GUI 창이 실행된다.

이 파일을 .exe 파일로 변환하자.

해당 파이썬 파일이 있는 위치에서 command 창을 띄운다.

주소창에 cmd 를 입력하거나 Shift 를 누른채로 빈 곳에 우클릭하여 명령창을 실행한다.

실행한 command 창에 pyinstaller와 파일이름을 같이 입력한다.

pyinstaller gui.py

여러개의 파일이 생성된 것을 확인할 수 있다.

dist라는 파일 안에 gui 라는 폴더를 클릭한다.

gui.exe 라는 실행 파일이 생성된 것을 확인할 수 있다.

해당 파일을 실행시키면,

이전과 같이 GUI 창이 실행된 것을 확인할 수 있다.

9. Python 데이터 분석 소개

9.1. 주요 분석 도구 소개

9.1.1. pandas

Pandas는 데이터를 쉽게 가공할 수 있도록 돕는다. SQL, 엑셀 파일을 표로 읽어올 수 있으며, 데이터 분석하는데 효과적인 자료구조와 기능들을 제공한다.

9.1.2. matplotlib

Matplotlib는 자료를 차트나 플롯으로 시각화하는 도구이다. 많은 파이썬 시각화 툴의 기본이 되며, 제공하는 기능이 많지만 초보자에게 사용 방법이 어렵다고 느껴질 수 있다.

9.1.3. seaborn

Seaborn은 matplotlib를 바탕으로 만든 시각화 툴이다. Pandas의 데이터 프레임을 사용해 더욱 쉽게 그래프를 그릴 수 있도록 돕는다.

9.1.4. statsmodels

Statsmodels는 python에서 다양한 통계 분석을 할 수 있도록 기능을 제공한다.

9.1.5. scikit-learn

Scikit-learn은 python에서 머신러닝을 쉽게 적용할 수 있도록 여러 기능을 제공한다.

9.2. 분석 도구 예시

9.2.1. 자료 가져오기 (pandas)

Pandas로 자료를 가져올 수 있으며 분석에 적합하도록 가공을 할 수 있다.

import pandas as pd   # pandas 를 pd 로 불러온다.
  

"Guerry" 데이터 셋을 사용해보자.

"Guerry" 데이터 셋은 A.-M. Guerry의 "Essay on the Moral Statistics of France" 연구에서 공개된 자료로, 1830년도 프랑스의 사회인구학적 데이터를 담고 있다.

다음은 이 데이터 셋의 몇 가지 지표 예시이다.

url = 'https://vincentarelbundock.github.io/Rdatasets/csv/HistData/Guerry.csv'
  
original_df = pd.read_csv(url)
  
original_df = pd.read_csv(url, index_col=0)
  # `index_col=0`은 첫 열을 index로 지정
  

head()로 데이터프레임의 첫 5행을 확인할 수 있다. .head()에 특정 숫자를 넘겨주면 해당 숫자 만큼의 행을 출력한다.

original_df.head()
  
dept Region Department Crime_pers Crime_prop Literacy Donations Infants Suicides MainCity ... Crime_parents Infanticide Donation_clergy Lottery Desertion Instruction Prostitutes Distance Area Pop1831
1 1 E Ain 28870 15890 37 5098 33120 35039 2:Med ... 71 60 69 41 55 46 13 218.372 5762 346.03
2 2 N Aisne 26226 5521 51 8901 14572 12831 2:Med ... 4 82 36 38 82 24 327 65.945 7369 513.00
3 3 C Allier 26747 7925 13 10973 17044 114121 2:Med ... 46 42 76 66 16 85 34 161.927 7340 298.26
4 4 E Basses-Alpes 12935 7289 46 2733 23018 14238 1:Sm ... 70 12 37 80 32 29 2 351.399 6925 155.90
5 5 E Hautes-Alpes 17488 8174 69 6962 23076 16171 1:Sm ... 22 23 64 79 35 7 1 320.280 5549 129.10

5 rows × 23 columns

자료의 대략적인 모양을 확인할 수 있다.

.tail().head()와 반대로 자료의 끝부분을 확인해볼 수 있다.

original_df.tail()
  
dept Region Department Crime_pers Crime_prop Literacy Donations Infants Suicides MainCity ... Crime_parents Infanticide Donation_clergy Lottery Desertion Instruction Prostitutes Distance Area Pop1831
82 86 W Vienne 15010 4710 25 8922 35224 21851 2:Med ... 20 1 44 40 38 65 18 170.523 6990 282.73
83 87 C Haute-Vienne 16256 6402 13 13817 19940 33497 2:Med ... 68 6 78 55 11 84 7 198.874 5520 285.13
84 88 E Vosges 18835 9044 62 4040 14978 33029 2:Med ... 58 34 5 14 85 11 43 174.477 5874 397.99
85 89 C Yonne 18006 6516 47 4276 16616 12789 2:Med ... 32 22 35 51 66 27 272 81.797 7427 352.49
86 200 NaN Corse 2199 4589 49 37015 24743 37016 2:Med ... 81 2 84 83 9 25 1 539.213 8680 195.41

5 rows × 23 columns

86번째 행의 "Region" 열에 NaN 이 보인다. NaN은 'Not-a-Number'의 줄임말로 값이 없다는 뜻이다. 이를 .dropna() 메소드로 제거해보자.

inplace=True 옵션을 넘겨주면 실행결과를 현재 변수에 다시 저장한다. inplace=False 옵션을 넘겨주면 실행결과가 출력만 되고 실제 데이터 프레임에는 변화가 없으므로 새로운 변수에 저장해줘야한다.

original_df.dropna(inplace=True)
  

다시 확인해보니 NaN 이 들어있던 행은 제거되었다.

original_df.tail()
  
dept Region Department Crime_pers Crime_prop Literacy Donations Infants Suicides MainCity ... Crime_parents Infanticide Donation_clergy Lottery Desertion Instruction Prostitutes Distance Area Pop1831
81 85 W Vendee 20827 7566 28 14035 62486 67963 1:Sm ... 50 44 30 68 79 59 4 212.459 6720 330.36
82 86 W Vienne 15010 4710 25 8922 35224 21851 2:Med ... 20 1 44 40 38 65 18 170.523 6990 282.73
83 87 C Haute-Vienne 16256 6402 13 13817 19940 33497 2:Med ... 68 6 78 55 11 84 7 198.874 5520 285.13
84 88 E Vosges 18835 9044 62 4040 14978 33029 2:Med ... 58 34 5 14 85 11 43 174.477 5874 397.99
85 89 C Yonne 18006 6516 47 4276 16616 12789 2:Med ... 32 22 35 51 66 27 272 81.797 7427 352.49

5 rows × 23 columns

이 중 재산범죄, 문해율, 재산순위, 수도까지의 거리에 대한 자료만 선택해서 다른 데이터 프레임으로 저장하자.

df = original_df[['Crime_prop', 'Literacy', 'Wealth', 'Distance']]
  
df.head()
  
Crime_prop Literacy Wealth Distance
1 15890 37 73 218.372
2 5521 51 22 65.945
3 7925 13 61 161.927
4 7289 46 76 351.399
5 8174 69 83 320.280

새 데이터 프레임이 만들어졌다.

9.2.2. OLS 회귀분석 (statsmodels)

전처리한 자료에 회귀분석을 적용해 변수별 회귀계수를 구할 수 있다.

import statsmodels.api as sm
  
import statsmodels.formula.api as smf
  

문해율, 재산순위, 수도까지의 거리가 재산범죄에 미치는 영향을 살펴보자.

예시로 Ordinary Least Squares(OLS) 방식을 사용해보자.

res = smf.ols(formula='Crime_prop ~ Literacy + Wealth + Distance', data=df).fit()
  
res
  
<statsmodels.regression.linear_model.RegressionResultsWrapper at 0x1f9254eddd8>

분석에 대한 요약을 한눈에 볼 수 있다.

print(res.summary())
  

9.2.3. 시각화 (matplotlib, seaborn)

각 변수에 대한 재산범죄의 관계를 그래프로 그려보자.

%matplotlib inline
  
import seaborn as sns
  
import matplotlib.pyplot as plt
  

plt.subplot()은 subplot을 생성한다. plt.subplot(2, 3, 3)은 2 x 3 그래프에서 3번째에 있는 그래프를 뜻한다.

.regplot()은 데이터와 선형 회귀 모형을 그래프로 나타낸다.

plt.subplot(1, 3, 1)   # 1 x 3 그래프 배열에서 첫 번째 그래프
  sns.regplot('Literacy', 'Crime_prop', df, color='red')
  plt.subplot(1, 3, 2)   # 1 x 3 그래프 배열에서 두 번째 그래프
  sns.regplot('Wealth', 'Crime_prop', df, color='blue')
  plt.subplot(1, 3, 3)   # 1 x 3 그래프 배열에서 세 번째 그래프
  sns.regplot('Distance', 'Crime_prop', df, color='green')
  
<matplotlib.axes._subplots.AxesSubplot at 0x1f927a68cc0>
<matplotlib.figure.Figure at 0x1f9279253c8>

문해율은 높을수록 재산범죄당 인구가 줄어들며, 재산순위가 높고 수도까지의 거리가 멀어질수록 재산범죄당 인구가 늘어난다.

pandas plot

데이터 프레임에서 바로 .plot()을 실행하여 그래프를 그릴 수 있다.

df.plot()
  
<matplotlib.axes._subplots.AxesSubplot at 0x1eddc479048>
<matplotlib.figure.Figure at 0x1eddc22c5c0>

특정 그래프를 그리고 싶다면 kind= 옵션에 원하는 그래프 타입을 넘겨준다.

예를 들어 산점도를 그릴 경우, kind='scatter'라고 지정한다. 이때 x와 y 축에 해당하는 컬럼의 이름을 넘겨줘야 한다.

df.plot('Literacy', 'Crime_prop', kind='scatter',color='red')
  df.plot('Wealth', 'Crime_prop', kind='scatter', color='blue')
  
<matplotlib.axes._subplots.AxesSubplot at 0x1ede072f4e0>
<matplotlib.figure.Figure at 0x1eddf31ef28>
<matplotlib.figure.Figure at 0x1ede080ae10>

9.2.4. 예측 (scikit-learn)

이번에는 자료를 학습시켜 재산범죄를 예측해보자.

from sklearn.linear_model import LinearRegression
  

먼저 선형 회귀 모형을 불러온다.

model = LinearRegression()
  

변수 x에 학습 자료를 지정하고, 변수 y에 정답 자료를 지정한다.

x = df[['Literacy', 'Wealth', 'Distance']]  # 학습 자료
  y = df['Crime_prop']  # 정답 자료
  

.fit()을 통하여 학습자료와 정답자료를 학습시킨다.

model.fit(x, y)
  
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)

동네 A

동네A의 재산범죄를 예측해보자.

모형에 .predict() 메소드 실행하여 예측할 수 있다.

literacy = 80
  wealth = 1
  distance = 20

  regionA = [literacy, wealth, distance]

  crimeA = model.predict([regionA])

  crimeA
  
array([ 3880.68993851])

3천명당 1건의 재산범죄를 예측할 수 있다.

동네 B

동네B의 재산범죄를 예측해보자.

literacy = 10
  wealth = 70
  distance = 500

  regionB = [literacy, wealth, distance]

  crimeB = model.predict([regionB])

  crimeB
  
array([ 10964.81313077])

1만명당 1건의 재산범죄를 예측할 수 있다.

동네 C

두 예시의 중간 수준의 동네C의 재산범죄를 예측해보자.

literacy = 50
  wealth = 30
  distance = 300

  regionC = [literacy, wealth, distance]

  crimeC = model.predict([regionC])

  crimeC
  
array([ 7073.04508028])

6천명당 1건의 재산범죄를 예측할 수 있다.

10. pandas

10.1. 판다스로 파일 다루기

10.1.1. 파일 불러오기

다양한 형태의 자료를 판다스로 불러오고 내보내보자.

import pandas as pd
  

csv

csv 파일은 Comma-Separated Values 를 의미한다. 즉, (, ;, \t) 등으로 구분이 되어있는 자료를 의미한다.

csv 자료를 불러올때는 .read_csv 를 사용한다.

bank = pd.read_csv('bank.csv')  # bank 로 저장
  

자료가 불려왔는지 확인해보자.

bank.head()
  
age;"job";"marital";"education";"default";"balance";"housing";"loan";"contact";"day";"month";"duration";"campaign";"pdays";"previous";"poutcome";"y"
0 30;"unemployed";"married";"primary";"no";1787;...
1 33;"services";"married";"secondary";"no";4789;...
2 35;"management";"single";"tertiary";"no";1350;...
3 30;"management";"married";"tertiary";"no";1476...
4 59;"blue-collar";"married";"secondary";"no";0;...

자료가 ; 로 구분이 되어있는데, 구분이 이뤄지지 않았다.

sep 옵션에 ; 를 넘겨주면 된다. sep 는 separator의 약자로 무엇으로 구분할지 지정할 수 있다.

separator

;
bank = pd.read_csv('bank.csv', sep=';')
  
bank.head()
  
age job marital education default balance housing loan contact day month duration campaign pdays previous poutcome y
0 30 unemployed married primary no 1787 no no cellular 19 oct 79 1 -1 0 unknown no
1 33 services married secondary no 4789 yes yes cellular 11 may 220 1 339 4 failure no
2 35 management single tertiary no 1350 yes no cellular 16 apr 185 1 330 1 failure no
3 30 management married tertiary no 1476 yes yes unknown 3 jun 199 4 -1 0 unknown no
4 59 blue-collar married secondary no 0 yes no unknown 5 may 226 1 -1 0 unknown no

csv 파일이 바르게 불려왔다.

header

http://archive.ics.uci.edu/ml/datasets/Adult 에서 데이터를 다운 받는다. 이번 파일은 사람들의 성별, 나이, 노동시간 등의 정보를 담고 있다.

adult = pd.read_csv('adult.data')
  
adult.head()
  
39 State-gov 77516 Bachelors 13 Never-married Adm-clerical Not-in-family White Male 2174 0 40 United-States <=50K
0 50 Self-emp-not-inc 83311 Bachelors 13 Married-civ-spouse Exec-managerial Husband White Male 0 0 13 United-States <=50K
1 38 Private 215646 HS-grad 9 Divorced Handlers-cleaners Not-in-family White Male 0 0 40 United-States <=50K
2 53 Private 234721 11th 7 Married-civ-spouse Handlers-cleaners Husband Black Male 0 0 40 United-States <=50K
3 28 Private 338409 Bachelors 13 Married-civ-spouse Prof-specialty Wife Black Female 0 0 40 Cuba <=50K
4 37 Private 284582 Masters 14 Married-civ-spouse Exec-managerial Wife White Female 0 0 40 United-States <=50K

이 자료의 특징으로는, 열의 이름이 없어, 첫 행이 열의 이름으로 불려왔다는 점이다.

이런 상황을 예방하고 싶다면, header 라는 옵션을 None으로 지정하면 된다.

adult = pd.read_csv('adult.data', header=None)
  
adult.head()
  
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
0 39 State-gov 77516 Bachelors 13 Never-married Adm-clerical Not-in-family White Male 2174 0 40 United-States <=50K
1 50 Self-emp-not-inc 83311 Bachelors 13 Married-civ-spouse Exec-managerial Husband White Male 0 0 13 United-States <=50K
2 38 Private 215646 HS-grad 9 Divorced Handlers-cleaners Not-in-family White Male 0 0 40 United-States <=50K
3 53 Private 234721 11th 7 Married-civ-spouse Handlers-cleaners Husband Black Male 0 0 40 United-States <=50K
4 28 Private 338409 Bachelors 13 Married-civ-spouse Prof-specialty Wife Black Female 0 0 40 Cuba <=50K

excel

이번에는 엑셀 파일을 불러와보자. http://archive.ics.uci.edu/ml/datasets/Forest+Fires 에서 데이터를 다운받아 엑셀 파일로 먼저 변환을 한다. 엑셀파일은 .read_excel로 불러올 수 있다.

forest = pd.read_excel('forestfires.xlsx')
  
forest.head()
  
X Y month day FFMC DMC DC ISI temp RH wind rain area
0 7 5 mar fri 86.2 26.2 94.3 5.1 8.2 51 6.7 0.0 0.0
1 7 4 oct tue 90.6 35.4 669.1 6.7 18.0 33 0.9 0.0 0.0
2 7 4 oct sat 90.6 43.7 686.9 6.7 14.6 33 1.3 0.0 0.0
3 8 6 mar fri 91.7 33.3 77.5 9.0 8.3 97 4.0 0.2 0.0
4 8 6 mar sun 89.3 51.3 102.2 9.6 11.4 99 1.8 0.0 0.0

10.1.2. 파일로 저장하기

to_csv

.to_csv로 데이터 프레임을 파일로 저장할 수 있다.

forest.to_csv('forest.csv')
  

해당 파일이 저장되었는지 확인해보자.

import os
  
os.path.exists('forest.csv')    # 파일이 존재하는지 확인
  
True

header & index

이번에는 adult를 csv 로 저장해보자.

adult.to_csv('adult2.csv')
  

파일을 확인해보니, 행 이름은 전에 연습삼아 지정해놓은 대로 저장되었으며, 열 이름은 숫자로 되어있다.

header옵션과 index 를 지정하면, 이를 해결할 수 있다.

adult.to_csv('adult2.csv', header=0, index=0)
  

다시 확인해보니, 이번에는 행과 열 이름이 사라졌다.

10.2. 데이터 프레임 다루기

데이터 프레임을 다루는 구체적인 방법을 살펴보자.

import pandas as pd
  

10.2.1. csv 데이터 불러오기

예제 데이터를 불러와보자.

예제 데이터는 포르투갈에서 발생한 산불과 관련된 데이터이다.

df = pd.read_csv('forest.csv')
  
df.head()
  
Unnamed: 0 X Y month day FFMC DMC DC ISI temp RH wind rain area
0 0 7 5 mar fri 86.2 26.2 94.3 5.1 8.2 51 6.7 0.0 0.0
1 1 7 4 oct tue 90.6 35.4 669.1 6.7 18.0 33 0.9 0.0 0.0
2 2 7 4 oct sat 90.6 43.7 686.9 6.7 14.6 33 1.3 0.0 0.0
3 3 8 6 mar fri 91.7 33.3 77.5 9.0 8.3 97 4.0 0.2 0.0
4 4 8 6 mar sun 89.3 51.3 102.2 9.6 11.4 99 1.8 0.0 0.0

10.2.2. 열 접근

데이터 프레임에서 원하는 값을 다양한 방법으로 선택할 수 있다.

[] 안에 열의 이름을 넣으면 열만 선택할 수 있다.

df['month'].head()
  
0    mar
  1    oct
  2    oct
  3    mar
  4    mar
  Name: month, dtype: object

하나의 열은 Series 형태로 나온다. 여기에서 Series 에서 작동하듯, indexing/slicing 을 통해 자료에 접근할 수 있다.

df['month'][0]    # 열의 첫번째 값
  
'mar'
df['month'][:5]  # 첫 다섯개 값
  
0    mar
  1    oct
  2    oct
  3    mar
  4    mar
  Name: month, dtype: object

하위 데이터 프레임

[[]] 를 사용해 하위 데이터 프레임을 만들 수 있다.

df[['month', 'day', 'rain']].head()
  
month day rain
0 mar fri 0.0
1 oct tue 0.0
2 oct sat 0.0
3 mar fri 0.2
4 mar sun 0.0

month, day, rain 의 열을 갖는 첫 10 행만 담는 데이터 프레임을 만들어보자.

df[['month', 'day', 'rain']][:10]
  
month day rain
0 mar fri 0.0
1 oct tue 0.0
2 oct sat 0.0
3 mar fri 0.2
4 mar sun 0.0
5 aug sun 0.0
6 aug mon 0.0
7 aug mon 0.0
8 sep tue 0.0
9 sep sat 0.0

10.2.3. 행 접근

행에 접근할때는 .loc 이나 .iloc 을 사용한다. .loc 은 행의 이름으로 정보를 찾을때 사용한다. .iloc 은 행의 index 번호로 자료를 찾을 때 사용한다.

df.loc[0]    # 행 이름이 0 인 자료 정보
  
Unnamed: 0       0
  X                7
  Y                5
  month          mar
  day            fri
  FFMC          86.2
  DMC           26.2
  DC            94.3
  ISI            5.1
  temp           8.2
  RH              51
  wind           6.7
  rain             0
  area             0
  Name: 0, dtype: object
df.iloc[0]  # 첫 행 정보
  
Unnamed: 0       0
  X                7
  Y                5
  month          mar
  day            fri
  FFMC          86.2
  DMC           26.2
  DC            94.3
  ISI            5.1
  temp           8.2
  RH              51
  wind           6.7
  rain             0
  area             0
  Name: 0, dtype: object

뒤에 열 이름으로 색인해주면 한 자료에 접근할 수 있다.

df.iloc[0]['temp']
  
8.1999999999999993

이를 다르게 표현할 수 있다.

df.loc[0, 'temp']
  
8.1999999999999993

slicing 을 사용해 여러 값을 선택할 수 있다.

df.iloc[:3]['temp']
  
0     8.2
  1    18.0
  2    14.6
  Name: temp, dtype: float64

하위 데이터 프레임

.iloc에서 indexing 을 사용해 하위 데이터 프레임을 만들 수 있다.

df.iloc[:5]  # 첫 다섯개 행으로 하위 데이터 프레임 만들기
  
Unnamed: 0 X Y month day FFMC DMC DC ISI temp RH wind rain area
0 0 7 5 mar fri 86.2 26.2 94.3 5.1 8.2 51 6.7 0.0 0.0
1 1 7 4 oct tue 90.6 35.4 669.1 6.7 18.0 33 0.9 0.0 0.0
2 2 7 4 oct sat 90.6 43.7 686.9 6.7 14.6 33 1.3 0.0 0.0
3 3 8 6 mar fri 91.7 33.3 77.5 9.0 8.3 97 4.0 0.2 0.0
4 4 8 6 mar sun 89.3 51.3 102.2 9.6 11.4 99 1.8 0.0 0.0

행의 이름으로 하위 데이터 프레임을 만들고 싶을 경우, .loc[[]] 를 사용해 하위 데이터 프레임을 만들 수 있다.

df.loc[[0, 3, 5]]    # 0, 3, 5 라는 이름을 가진 행을 가진 데이터 프레임
  
Unnamed: 0 X Y month day FFMC DMC DC ISI temp RH wind rain area
0 0 7 5 mar fri 86.2 26.2 94.3 5.1 8.2 51 6.7 0.0 0.0
3 3 8 6 mar fri 91.7 33.3 77.5 9.0 8.3 97 4.0 0.2 0.0
5 5 8 6 aug sun 92.3 85.3 488.0 14.7 22.2 29 5.4 0.0 0.0

at

하나의 값에 접근할 때, .at를 사용하기도 한다.

df.at[0, 'month']    # 행의 이름이 0 이며 열의 이름이 month 인 정보
  
'mar'

10.2.4. set index

현재 행의 이름(index)는 숫자로 되어있다. 숫자가 아닌 열의 정보로 행의 이름을 지정하고 싶다면, .set_index() 를 사용한다.

현재 index 는 .index 로 확인할 수 있다.

df.index    # 0-517 까지의 숫자
  
RangeIndex(start=0, stop=517, step=1)

X 값을 index 로 지정해보자.

df.set_index('X').head()
  
Unnamed: 0 Y month day FFMC DMC DC ISI temp RH wind rain area
X
7 0 5 mar fri 86.2 26.2 94.3 5.1 8.2 51 6.7 0.0 0.0
7 1 4 oct tue 90.6 35.4 669.1 6.7 18.0 33 0.9 0.0 0.0
7 2 4 oct sat 90.6 43.7 686.9 6.7 14.6 33 1.3 0.0 0.0
8 3 6 mar fri 91.7 33.3 77.5 9.0 8.3 97 4.0 0.2 0.0
8 4 6 mar sun 89.3 51.3 102.2 9.6 11.4 99 1.8 0.0 0.0

index 가 지정되었다. 이를 df2에 저장하자.

df2 = df.set_index('X')
  
df2.head()  # 확인
  
Unnamed: 0 Y month day FFMC DMC DC ISI temp RH wind rain area
X
7 0 5 mar fri 86.2 26.2 94.3 5.1 8.2 51 6.7 0.0 0.0
7 1 4 oct tue 90.6 35.4 669.1 6.7 18.0 33 0.9 0.0 0.0
7 2 4 oct sat 90.6 43.7 686.9 6.7 14.6 33 1.3 0.0 0.0
8 3 6 mar fri 91.7 33.3 77.5 9.0 8.3 97 4.0 0.2 0.0
8 4 6 mar sun 89.3 51.3 102.2 9.6 11.4 99 1.8 0.0 0.0

10.2.5. inplace

df를 확인해보면, .set_index가 적용되지 않았다.

df.head()
  
Unnamed: 0 X Y month day FFMC DMC DC ISI temp RH wind rain area
0 0 7 5 mar fri 86.2 26.2 94.3 5.1 8.2 51 6.7 0.0 0.0
1 1 7 4 oct tue 90.6 35.4 669.1 6.7 18.0 33 0.9 0.0 0.0
2 2 7 4 oct sat 90.6 43.7 686.9 6.7 14.6 33 1.3 0.0 0.0
3 3 8 6 mar fri 91.7 33.3 77.5 9.0 8.3 97 4.0 0.2 0.0
4 4 8 6 mar sun 89.3 51.3 102.2 9.6 11.4 99 1.8 0.0 0.0

pandas의 많은 메써드는 원본 데이터 프레임에 변화를 자동으로 저장하지 않는다. 만약 같은 변수의 이름의 데이터 프레임에 가공을 하고 싶다면, inplace=True 옵션을 넘겨 항상 새로운 변수에 저장하지 않아도 되는 번거로움을 없앨 수 있다.

df.set_index('X', inplace=True)
  
df.head()
  
Unnamed: 0 Y month day FFMC DMC DC ISI temp RH wind rain area
X
7 0 5 mar fri 86.2 26.2 94.3 5.1 8.2 51 6.7 0.0 0.0
7 1 4 oct tue 90.6 35.4 669.1 6.7 18.0 33 0.9 0.0 0.0
7 2 4 oct sat 90.6 43.7 686.9 6.7 14.6 33 1.3 0.0 0.0
8 3 6 mar fri 91.7 33.3 77.5 9.0 8.3 97 4.0 0.2 0.0
8 4 6 mar sun 89.3 51.3 102.2 9.6 11.4 99 1.8 0.0 0.0

편의를 위해 df 를 원래대로 돌려놓자

df = pd.read_csv('forest.csv')
  

10.2.6. reindex

열 혹은 행의 순서를 바꾸고 싶을때 .reindex 를 사용한다.

열 순서 바꾸기

df 의 컬럼을 month, day, X, Y 로 바꿔보자.

df.reindex(columns=['month', 'day', 'X', 'Y']).head()
  
month day X Y
0 mar fri 7 5
1 oct tue 7 4
2 oct sat 7 4
3 mar fri 8 6
4 mar sun 8 6

index 순서 바꾸기

행의 순서도 바꿀 수 있다. 행의 이름을 기준으로, 순서가 4, 3, 2, 1, 0 이 되도록 바꿔보자.

df.reindex(index=[4, 3, 2, 1, 0])
  
Unnamed: 0 X Y month day FFMC DMC DC ISI temp RH wind rain area
4 4 8 6 mar sun 89.3 51.3 102.2 9.6 11.4 99 1.8 0.0 0.0
3 3 8 6 mar fri 91.7 33.3 77.5 9.0 8.3 97 4.0 0.2 0.0
2 2 7 4 oct sat 90.6 43.7 686.9 6.7 14.6 33 1.3 0.0 0.0
1 1 7 4 oct tue 90.6 35.4 669.1 6.7 18.0 33 0.9 0.0 0.0
0 0 7 5 mar fri 86.2 26.2 94.3 5.1 8.2 51 6.7 0.0 0.0

10.3. sorting

데이터 프레임 정렬에 대해 배워보자.

import pandas as pd
  

예제로 성인들의 인적 정보가 담긴 adult 예제를 살펴보자

df = pd.read_csv('adult.csv', header=None)
  

이 자료는 header 가 없기 때문에, 따로 열 이름을 지정해줘야 한다. 자세한 사항은 rename 강의에 나와있다.

titles = {0: '나이', 1: '고용형태', 2: '가중치',
            3: '최종학위', 4:'교육햇수', 5: '결혼유무',
            6: '직업명', 7:'가족내역할', 8:'인종',
            9: '성별', 10: '자본이득', 11: '자본손실',
            12: '노동시간', 13:'고향', 14:'수입'
           }
  
df.rename(columns=titles, inplace=True)
  
df.head()
  
나이 고용형태 가중치 최종학위 교육햇수 결혼유무 직업명 가족내역할 인종 성별 자본이득 자본손실 노동시간 고향 수입
0 39 State-gov 77516 Bachelors 13 Never-married Adm-clerical Not-in-family White Male 2174 0 40 United-States <=50K
1 50 Self-emp-not-inc 83311 Bachelors 13 Married-civ-spouse Exec-managerial Husband White Male 0 0 13 United-States <=50K
2 38 Private 215646 HS-grad 9 Divorced Handlers-cleaners Not-in-family White Male 0 0 40 United-States <=50K
3 53 Private 234721 11th 7 Married-civ-spouse Handlers-cleaners Husband Black Male 0 0 40 United-States <=50K
4 28 Private 338409 Bachelors 13 Married-civ-spouse Prof-specialty Wife Black Female 0 0 40 Cuba <=50K

10.3.1. 기본 정렬

일단, 나이 순으로 자료를 정렬해보자. 정렬은 .sort_values() 를 이용한다.

df.sort_values('나이').head()
  
나이 고용형태 가중치 최종학위 교육햇수 결혼유무 직업명 가족내역할 인종 성별 자본이득 자본손실 노동시간 고향 수입
12318 17 Private 127366 11th 7 Never-married Sales Own-child White Female 0 0 8 United-States <=50K
6312 17 Private 132755 11th 7 Never-married Sales Own-child White Male 0 0 15 United-States <=50K
30927 17 Private 108470 11th 7 Never-married Other-service Own-child Black Male 0 0 17 United-States <=50K
12787 17 Local-gov 308901 11th 7 Never-married Adm-clerical Own-child White Female 0 0 15 United-States <=50K
25755 17 NaN 47407 11th 7 Never-married NaN Own-child White Male 0 0 10 United-States <=50K

나이가 적은 순으로 정렬되었다.

10.3.2. 내림차순 정렬

정렬 순서를 바꾸고 싶다면 ascending=False 옵션을 지정해주면 된다. ascending 의 초기값은 True로 오름차순이 기본 설정이다.

df.sort_values('나이', ascending=False).head()
  
나이 고용형태 가중치 최종학위 교육햇수 결혼유무 직업명 가족내역할 인종 성별 자본이득 자본손실 노동시간 고향 수입
5406 90 Private 51744 Masters 14 Never-married Exec-managerial Not-in-family Black Male 0 0 50 United-States >50K
6624 90 Private 313986 11th 7 Married-civ-spouse Craft-repair Husband White Male 0 0 40 United-States <=50K
20610 90 Private 206667 Masters 14 Married-civ-spouse Prof-specialty Wife White Female 0 0 40 United-States >50K
1040 90 Private 137018 HS-grad 9 Never-married Other-service Not-in-family White Female 0 0 40 United-States <=50K
1935 90 Private 221832 Bachelors 13 Married-civ-spouse Exec-managerial Husband White Male 0 0 45 United-States <=50K

나이가 많은 순으로 정렬 되었다.

10.3.3. 다중 정렬

한번에 여러 열을 정렬할 수 있다.

0번 열의 나이는 내림차순, 4번 열의 교육 햇수는 오름차순으로 정렬해보자.

df.sort_values(['나이', '교육햇수'], ascending=[False, True]).head()
  
나이 고용형태 가중치 최종학위 교육햇수 결혼유무 직업명 가족내역할 인종 성별 자본이득 자본손실 노동시간 고향 수입
24238 90 NaN 166343 1st-4th 2 Widowed NaN Not-in-family Black Female 0 0 40 United-States <=50K
19747 90 Private 226968 7th-8th 4 Married-civ-spouse Machine-op-inspct Husband White Male 0 0 40 United-States <=50K
25303 90 NaN 175444 7th-8th 4 Separated NaN Not-in-family White Female 0 0 15 United-States <=50K
32367 90 Local-gov 214594 7th-8th 4 Married-civ-spouse Protective-serv Husband White Male 2653 0 40 United-States <=50K
5272 90 Private 141758 9th 5 Never-married Adm-clerical Not-in-family White Female 0 0 40 United-States <=50K

나이는 많지만 교육 햇수는 적은 순으로 정렬되었다.

10.3.4. index 정렬

위에서는 값을 기준으로 정렬시켰지만, 이번에는 행의 이름을 기준으로 정렬을 해보자.

먼저 6번 직업명의 열을 행이름으로 설정하자.

df.set_index('직업명').head()
  
나이 고용형태 가중치 최종학위 교육햇수 결혼유무 가족내역할 인종 성별 자본이득 자본손실 노동시간 고향 수입
직업명
Adm-clerical 39 State-gov 77516 Bachelors 13 Never-married Not-in-family White Male 2174 0 40 United-States <=50K
Exec-managerial 50 Self-emp-not-inc 83311 Bachelors 13 Married-civ-spouse Husband White Male 0 0 13 United-States <=50K
Handlers-cleaners 38 Private 215646 HS-grad 9 Divorced Not-in-family White Male 0 0 40 United-States <=50K
Handlers-cleaners 53 Private 234721 11th 7 Married-civ-spouse Husband Black Male 0 0 40 United-States <=50K
Prof-specialty 28 Private 338409 Bachelors 13 Married-civ-spouse Wife Black Female 0 0 40 Cuba <=50K

이를 df2라고 저장하자.

df2 = df.set_index('직업명')
  

이제 행 이름을 기준으로 정렬해보자. 행 이름을 기준으로 정렬은 .sort_index()를 사용한다.

df2.sort_index().head()
  
나이 고용형태 가중치 최종학위 교육햇수 결혼유무 가족내역할 인종 성별 자본이득 자본손실 노동시간 고향 수입
직업명
Adm-clerical 39 State-gov 77516 Bachelors 13 Never-married Not-in-family White Male 2174 0 40 United-States <=50K
Adm-clerical 18 Private 210932 HS-grad 9 Never-married Own-child White Female 0 0 40 United-States <=50K
Adm-clerical 20 Private 159297 Some-college 10 Never-married Own-child Asian-Pac-Islander Female 0 0 15 United-States <=50K
Adm-clerical 26 Private 202203 Bachelors 13 Never-married Other-relative White Female 0 0 50 United-States <=50K
Adm-clerical 32 Private 211028 Some-college 10 Never-married Not-in-family White Female 0 0 40 United-States <=50K

직업명 기준으로 정렬되었다.

10.4. Filtering

데이터 프레임에서 원하는 조건의 값만 필터링 해보자.

예제 데이터는 고객의 은행 신상 정보다.

import pandas as pd
  bank = pd.read_csv('bank.csv', sep=';')
  
bank.head()
  
age job marital education default balance housing loan contact day month duration campaign pdays previous poutcome y
0 30 unemployed married primary no 1787 no no cellular 19 oct 79 1 -1 0 unknown no
1 33 services married secondary no 4789 yes yes cellular 11 may 220 1 339 4 failure no
2 35 management single tertiary no 1350 yes no cellular 16 apr 185 1 330 1 failure no
3 30 management married tertiary no 1476 yes yes unknown 3 jun 199 4 -1 0 unknown no
4 59 blue-collar married secondary no 0 yes no unknown 5 may 226 1 -1 0 unknown no

자료는 4,521개의 행과 17개의 열로 이루어진다.

bank.shape
  
(4521, 17)

10.4.1. 비교

비교연산자를 사용해 일정 조건에 충족하는 값만 추출해보자.

>, <

나이가 35세 이전인 값만 추출해보자.

under_35 변수에 이를 저장해보겠다.

under_35 = bank['age'] < 35
  
under_35.head()
  
0     True
  1     True
  2    False
  3     True
  4    False
  Name: age, dtype: bool

Series 안에 불리안 형태로 결과가 출력된다.

young = bank[under_35]
  
young.head()
  
age job marital education default balance housing loan contact day month duration campaign pdays previous poutcome y
0 30 unemployed married primary no 1787 no no cellular 19 oct 79 1 -1 0 unknown no
1 33 services married secondary no 4789 yes yes cellular 11 may 220 1 339 4 failure no
3 30 management married tertiary no 1476 yes yes unknown 3 jun 199 4 -1 0 unknown no
13 20 student single secondary no 502 no no cellular 30 apr 261 1 -1 0 unknown yes
14 31 blue-collar married secondary no 360 yes yes cellular 29 jan 89 1 241 1 failure no

youngshape를 확인해보니, 행의 갯수가 1,472개로 줄었다.

young.shape
  
(1472, 17)

따라서 young은 나이가 35세 이전인 사람들의 자료만 모아놓은 데이터 프레임이다.

이를 한번에 표현하면 다음과 같다.

young = bank[bank['age'] < 35]
  
young.shape
  
(1472, 17)

==

이번에는 marital 이 'married' 인 사람들만 추출해보자.

marital 열이 'married' 와 같다면, 이를 따로 모아 데이터 프레임을 만들자.

married = bank[bank['marital'] == 'married']
  
married.head()
  
age job marital education default balance housing loan contact day month duration campaign pdays previous poutcome y
0 30 unemployed married primary no 1787 no no cellular 19 oct 79 1 -1 0 unknown no
1 33 services married secondary no 4789 yes yes cellular 11 may 220 1 339 4 failure no
3 30 management married tertiary no 1476 yes yes unknown 3 jun 199 4 -1 0 unknown no
4 59 blue-collar married secondary no 0 yes no unknown 5 may 226 1 -1 0 unknown no
6 36 self-employed married tertiary no 307 yes no cellular 14 may 341 1 330 2 other no

필터링으로 인해 행의 수가 줄어든것을 볼 수 있다. 이제는 결혼한 사람들만 선택되었다.

married.shape
  
(2797, 17)

&

이번에는 두가지 조건을 모두 만족하는 값을 추출해보자. 두 조건이 모두 충족하는 값을 찾을때는 &를 사용한다.

이를 young_married에 저장해보자.

young_married = bank[(bank['marital'] == 'married') &
                       (bank['age'] < 35)]
  
young_married.head()
  
age job marital education default balance housing loan contact day month duration campaign pdays previous poutcome y
0 30 unemployed married primary no 1787 no no cellular 19 oct 79 1 -1 0 unknown no
1 33 services married secondary no 4789 yes yes cellular 11 may 220 1 339 4 failure no
3 30 management married tertiary no 1476 yes yes unknown 3 jun 199 4 -1 0 unknown no
14 31 blue-collar married secondary no 360 yes yes cellular 29 jan 89 1 241 1 failure no
19 31 services married secondary no 132 no no cellular 7 jul 148 1 152 1 other no

35세 이하의 기혼인 사람만 선택되었다.

young_married.shape
  
(639, 17)

|

이번에는 두가지 조건 중 하나만 충족하는 값을 추출해보자. 두 조건 중 하나만 충족하는 값을 찾을때는 | 를 사용한다.

young_married = bank[(bank['marital'] == 'married') |
                       (bank['age'] < 35)]
  
young_married.head()
  
age job marital education default balance housing loan contact day month duration campaign pdays previous poutcome y
0 30 unemployed married primary no 1787 no no cellular 19 oct 79 1 -1 0 unknown no
1 33 services married secondary no 4789 yes yes cellular 11 may 220 1 339 4 failure no
3 30 management married tertiary no 1476 yes yes unknown 3 jun 199 4 -1 0 unknown no
4 59 blue-collar married secondary no 0 yes no unknown 5 may 226 1 -1 0 unknown no
6 36 self-employed married tertiary no 307 yes no cellular 14 may 341 1 330 2 other no

35세 이하이거나 결혼한 사람들이 선택되었다.

young.shape
  
(1472, 17)

10.5. 간단한 열 정보 얻기

pandas 에서 손쉽게 열에 대해 통계 정보를 얻을 수 있다.

예제는 포르투갈에서 발생한 산불에 대한 정보이다.

import pandas as pd
  df = pd.read_excel('forestfires.xlsx')
  
df.head()   # d f 를 확인하겠습니다
  
X Y month day FFMC DMC DC ISI temp RH wind rain area
0 7 5 mar fri 86.2 26.2 94.3 5.1 8.2 51 6.7 0.0 0.0
1 7 4 oct tue 90.6 35.4 669.1 6.7 18.0 33 0.9 0.0 0.0
2 7 4 oct sat 90.6 43.7 686.9 6.7 14.6 33 1.3 0.0 0.0
3 8 6 mar fri 91.7 33.3 77.5 9.0 8.3 97 4.0 0.2 0.0
4 8 6 mar sun 89.3 51.3 102.2 9.6 11.4 99 1.8 0.0 0.0

X, Y 는 발생 좌표를 의미하며, month, day 는 발생 달 과, 요일을 의미한다.

그 외에도 다양한 건조주의보 수치, 온도, 습도, 강수량 등의 날씨 정보와, 피해 규모에 대한 정보를 담고 있다.

10.5.1. 통계

mean

데이터 프레임에서 원하는 열을 선택한 후, 뒤에 원하는 메써드를 부르는 방법으로 통계 값을 구할 수 있다. .mean()으로 평균 강수량을 살펴보자.

df['rain'].mean()
  
0.02166344294003869

sum

.sum()으로 총 면적을 구해보자.

df['area'].sum()
  
6642.049999999998

median

.median()으로 X 좌표의 중앙값을 구해보자.

df['X'].median()
  
4.0

mode

.mode()으로 Y 좌표의 최빈값을 구해보자.

df['Y'].mode()
  
0    4
  dtype: int64

standard deviation

.std()로 습도의 표준편차를 구해보자.

df['RH'].std()
  
16.317469239378394

10.5.2. 색인

min

이번에는 같은 방식으로 최고, 최저 값 등을 구해보자.

온도의 최소값을 구해보자.

df['temp'].min()
  
2.2000000000000002

max

온도의 최고값을 구해보자.

df['temp'].max()
  
33.299999999999997

idxmin

최저 온도의 행 위치를 보고 싶을때는 .idxmin()을 사용한다.

df['temp'].idxmin()
  
280

280 번째 행에 최저값이 위치해있다.

idxmax

최고 온도의 행 위치를 보자.

df['temp'].idxmax()
  
498

10.5.3. 요약 정보

unique

.unique() 는 등장한 값의 종류를 보여준다.

어떤 요일에 산불이 났는지 확인해보자.

df['day'].unique()
  
array(['fri', 'tue', 'sat', 'sun', 'mon', 'wed', 'thu'], dtype=object)

확인해보니, 모든 요일에 산불이 한번씩 났다.

nunique

.nunique()함수는 몇개의 값의 종류가 있는지 보여준다.

요일에 대한 값의 종류가 몇개인지 구해보자.

df['day'].nunique()
  
7

산불이 발생한 요일은 총 7개임을 알 수 있다.

value counts

value_counts()로 각 값의 출현 빈도를 구할 수 있다.

어느 달에 산불이 가장 많이 발생했는지 확인해보자.

df['month'].value_counts()
  
aug    184
  sep    172
  mar     54
  jul     32
  feb     20
  jun     17
  oct     15
  apr      9
  dec      9
  may      2
  jan      2
  nov      1
  Name: month, dtype: int64

확인결과, 8월과 9월에 산불이 가장 많이 발생했다.

11. 시각화 기초

주피터 노트북에서 시각화를 할 때는 항상 아래 명령을 입력해서 그래프가 노트북 안에 포함되도록 한다.

%matplotlib inline
  

seaborn은 Python 시각화에 가장 많이 사용되는 라이브러리 중에 하나이다. seabornmatplotlib를 기반으로 한다.

import seaborn as sns
  from matplotlib import pyplot as plt
  

11.1. 데이터 불러오기

실습에 사용할 데이터는 iris 데이터이다. iris 데이터는 seaborn에 내장되어 있어 아래 명령으로 불러올 수 있다.

df = sns.load_dataset('iris')
  

위의 명령이 안될 경우 https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv 에서 다운 받은 다음 pandas로 직접 읽어들인다.

import pandas
  df = pandas.read_csv('iris.csv')
  

어떤 방법으로든 데이터를 불러왔으면 데이터의 내용을 확인해보자.

df.head()
  
sepal_length sepal_width petal_length petal_width species
0 5.1 3.5 1.4 0.2 setosa
1 4.9 3.0 1.4 0.2 setosa
2 4.7 3.2 1.3 0.2 setosa
3 4.6 3.1 1.5 0.2 setosa
4 5.0 3.6 1.4 0.2 setosa

iris는 150 송이 붓꽃의 데이터이다. sepal은 꽃잎, petal은 꽃받침, species는 품종을 뜻한다.

11.2. 산점도

먼저 꽃잎의 길이와 꽃받침의 길이를 산점도로 나타내보자.

sns.regplot(x=df["sepal_length"], y=df["petal_length"])
  
<matplotlib.axes._subplots.AxesSubplot at 0x29d58b7bf28>
<matplotlib.figure.Figure at 0x29d588d1b00>

11.3. 색상

색상을 빨간색으로 바꿔보자.

sns.regplot(x=df["sepal_length"], y=df["petal_length"], color='red')
  
<matplotlib.axes._subplots.AxesSubplot at 0x29d58bb0470>
<matplotlib.figure.Figure at 0x29d58b6d470>

선의 속성만 바꾸려면 line_kws에 설정값을 사전 형태로 넘겨준다.

sns.regplot(x=df["sepal_length"], y=df["petal_length"], line_kws={'color': 'red'})
  
<matplotlib.axes._subplots.AxesSubplot at 0x29d58c21a20>
<matplotlib.figure.Figure at 0x29d58c5ea20>

점의 속성만 바꾸려면 scatter_kws에 설정값을 사전 형태로 넘겨준다.

sns.regplot(x=df["sepal_length"], y=df["petal_length"], scatter_kws={'color': 'red'})
  
<matplotlib.axes._subplots.AxesSubplot at 0x29d585f7710>
<matplotlib.figure.Figure at 0x29d58cde198>

색상에는 색상 코드를 사용할 수 있다. 색상 코드는 구글에서 "color picker"를 검색하자.

sns.regplot(x=df["sepal_length"], y=df["petal_length"], color='#f49842')
  
<matplotlib.axes._subplots.AxesSubplot at 0x29d58d305f8>
<matplotlib.figure.Figure at 0x29d58d50630>

11.4. 선 없애기

fit_reg 옵션으로 회귀선을 제거할 수 있다.

sns.regplot(x=df["sepal_length"], y=df["petal_length"], fit_reg=False)
  
<matplotlib.axes._subplots.AxesSubplot at 0x29d58d6bba8>
<matplotlib.figure.Figure at 0x29d58dab710>

11.5. 옵션

자세한 내용은 공식 문서를 참고하자. https://seaborn.pydata.org/generated/seaborn.regplot.html

line_kwsscatter_kws에 넘길 수 있는 옵션은 다음 링크를 참고하자.

11.6. 그래프의 속성 바꾸기

matplotlib에는 피겨(figure)와 액시즈(axes)라는 개념이 있다. 피겨는 그림을 가리키고, 액시즈는 그림에 포함된 하나의 그래프를 가리킨다.

seaborn의 그래프 함수들은 해당 그래프의 액시즈를 반환한다. 만약 그래프의 속성을 바꾸고 싶으면 해당 액시즈를 이용한다.

자세한 내용은 matplotlibhttps://matplotlib.org/api/axes_api.html 를 참고한다_api.html 를 참고한다.

ax = sns.regplot(x=df["sepal_length"], y=df["petal_length"])
  ax.set_ylim([0, 8])
  
(0, 8)
<matplotlib.figure.Figure at 0x29d58e341d0>

11.6.1. 축 이름 붙이기

축 이름은 set_xlabelset_ylabel로 붙인다.

ax.set_xlabel('Sepal Length')
  ax.figure  # 그림을 현재 셀에서 다시 보여준다
  
<matplotlib.figure.Figure at 0x29d58e341d0>

한글을 사용하면 글자가 깨진다.

ax.set_xlabel('꽃잎 길이')
  ax.figure
  
<matplotlib.figure.Figure at 0x29d58e341d0>

11.7. 글꼴 설정

다음과 같이 글꼴을 한글 글꼴로 바꿔준다.

plt.rc('font', family='Malgun Gothic')
  

다시 그래프를 그리면 한글이 잘 표시된다.

ax = sns.regplot(x=df["sepal_length"], y=df["petal_length"])
  ax.set_xlabel('꽃잎 길이')
  ax.set_ylabel('꽃받침 길이')
  
<matplotlib.text.Text at 0x29d58e93da0>
<matplotlib.figure.Figure at 0x29d58dd0be0>

위의 그래프를 자세히보면 왼쪽 아래 마이너스 표시가 깨지는 것을 볼 수 있다. 이것은 한글 폰트에 마이너스가 없기 때문에 생기는 현상이다. 다음과 같이 해주면 된다.

plt.rc('font', family='Malgun Gothic')
  plt.rc('axes', unicode_minus=False)
  
ax = sns.regplot(x=df["sepal_length"], y=df["petal_length"])
  ax.set_xlabel('꽃잎 길이')
  ax.set_ylabel('꽃받침 길이')
  
<matplotlib.text.Text at 0x29d58f09a90>
<matplotlib.figure.Figure at 0x29d59ef45f8>

11.8. 글꼴 이름 찾기

글꼴 이름이 무엇으로 되어있는지 모르는 경우 fontManager를 사용한다.

from matplotlib.font_manager import fontManager
  

아래와 같이 글꼴 이름에 Nanum이 들어가는 경우를 찾는다.

for font in fontManager.ttflist:
      if 'Nanum' in font.name:
          print(font.name)
  

11.9. 그림 속성 바꾸기

ax.figure를 통해 그림 속성을 바꿀 수도 있다.

matplotlibFigure 문서 참고

ax = sns.regplot(x=df["sepal_length"], y=df["petal_length"])
  ax.figure.set_size_inches(12, 10)
  
<matplotlib.figure.Figure at 0x1678fb70630>

11.10. 저장

그림을 저장하는 것도 피겨의 기능이다.

ax = sns.regplot(x=df["sepal_length"], y=df["petal_length"])
  ax.figure.savefig('lm.png')
  

11.11. 그래프의 스타일 조정

색상 등 그래프의 스타일을 조정할 경우 시본의 스타일 설정 기능을 사용한다. 자세한 내용은 https://seaborn.pydata.org/tutorial/aesthetics.html 문서를 참고한다.

sns.set_style('darkgrid')
  sns.regplot(x=df["sepal_length"], y=df["petal_length"])
  
<matplotlib.axes._subplots.AxesSubplot at 0x1678ff0d908>
<matplotlib.figure.Figure at 0x1678fddac88>

다음 명령으로 기본 스타일로 돌아올 수 있다.

sns.set()
  

11.12. 히스토그램과 밀도 그래프

산점도 두 변수의 관계를 보여준다면 히스토그램과 밀도 그래프는 한 변수의 분포를 보여준다.

sns.distplot(df["sepal_width"])
  
<matplotlib.axes._subplots.AxesSubplot at 0x1678fa803c8>
<matplotlib.figure.Figure at 0x1678fe6be80>

bins 옵션으로 막대의 갯수를 조절할 수 있다.

sns.distplot(df["sepal_width"], bins=4)
  
<matplotlib.axes._subplots.AxesSubplot at 0x16790066978>
<matplotlib.figure.Figure at 0x167900b34a8>

또는 구간을 명시할 수도 있다.

sns.distplot(df["sepal_width"], bins=[2, 2.5, 3, 3.5, 4, 4.5, 5])
  
<matplotlib.axes._subplots.AxesSubplot at 0x1679015a550>
<matplotlib.figure.Figure at 0x1679117ceb8>

numpy.arange를 사용하면 일정 구간으로 막대를 만들 수도 있다.

import numpy

  sns.distplot(df["sepal_width"], bins=numpy.arange(2, 5.5, 0.5))
  
<matplotlib.axes._subplots.AxesSubplot at 0x16791187470>
<matplotlib.figure.Figure at 0x167911ff780>

kde=False을 넘겨주면 밀도 그래프를 그리지 않는다.

sns.distplot(df["sepal_width"], kde=False)
  
<matplotlib.axes._subplots.AxesSubplot at 0x1678fa9ff60>
<matplotlib.figure.Figure at 0x1678fbd8898>

hist=False을 넘겨주면 히스토그램을 그리지 않는다.

sns.distplot(df["sepal_width"], hist=False)
  
<matplotlib.axes._subplots.AxesSubplot at 0x1678ff8be10>
<matplotlib.figure.Figure at 0x1678fe3ccc0>

자세한 내용은 https://seaborn.pydata.org/generated/seaborn.distplot.html 를 참고하자.

12. pandas II

12.1. 열끼리 계산

파이썬 연산 기호로 열끼리 간단한 계산을 할 수 있다.

예제로 성인 인적사항 자료를 사용하자.

import pandas as pd
  df = pd.read_csv('adult.csv', header=None)
  titles = {0: '나이', 1: '고용형태', 2: '가중치',
            3: '최종학위', 4:'교육햇수', 5: '결혼유무',
            6: '직업명', 7:'가족내역할', 8:'인종',
            9: '성별', 10: '자본이득', 11: '자본손실',
            12: '노동시간', 13:'고향', 14:'수입'
           }
  df.rename(columns=titles, inplace=True)
  
df.head()
  
나이 고용형태 가중치 최종학위 교육햇수 결혼유무 직업명 가족내역할 인종 성별 자본이득 자본손실 노동시간 고향 수입
0 39 State-gov 77516 Bachelors 13 Never-married Adm-clerical Not-in-family White Male 2174 0 40 United-States <=50K
1 50 Self-emp-not-inc 83311 Bachelors 13 Married-civ-spouse Exec-managerial Husband White Male 0 0 13 United-States <=50K
2 38 Private 215646 HS-grad 9 Divorced Handlers-cleaners Not-in-family White Male 0 0 40 United-States <=50K
3 53 Private 234721 11th 7 Married-civ-spouse Handlers-cleaners Husband Black Male 0 0 40 United-States <=50K
4 28 Private 338409 Bachelors 13 Married-civ-spouse Prof-specialty Wife Black Female 0 0 40 Cuba <=50K

12.1.1. 곱셈

열에 상수를 연산할 수 있다. 판다스는 각 행에 해당 연산을 적용시켜 돌려준다.

한 달 동안 근무 시간을 보기 위해 주당 근무 시간에 4 를 곱해보자.

hours_per_month = df['노동시간'] * 4
  

결과를 확인해보니, 월별 근무시간이 계산되었다.

hours_per_month.head()
  
0    160
  1     52
  2    160
  3    160
  4    160
  Name: 노동시간, dtype: int64

12.1.2. 나눗셈

반대로, 일당 근무 시간을 알아보기 위해 노동시간을 5 로 나누자.

hours_per_day = df['노동시간'] / 5
  

확인결과 일당 노동시간이 구해졌다.

hours_per_day.head()
  
0    8.0
  1    2.6
  2    8.0
  3    8.0
  4    8.0
  Name: 노동시간, dtype: float64

두번째 사람을 제외하고 첫 다섯 사람은 하루 8시간 근무한다.

12.1.3. 빼기

true gain

서로 다른 열을 사용해 계산할 수 있다.

- 로 자본이익에서 자본손실을 빼 true_gain에 저장하자.

true_gain = df['자본이득'] - df['자본손실']
  

확인해보니 차액이 구해졌다.

true_gain.head()
  
0    2174
  1       0
  2       0
  3       0
  4       0
  dtype: int64

근무 햇수

열과 열을 계산하며 상수를 포함해서 연산을 할 수 있다.

나이에서 교육받은 햇수와 유아기 8년을 빼, 근무 햇수를 구해보자.

not_educated = df['나이'] - df['교육햇수'] - 8
  

확인해보니, 모든 열에서 상수가 뺄셈되었다.

not_educated.head()
  
0    18
  1    29
  2    21
  3    38
  4     7
  dtype: int64

12.2. grouping

12.2.1. groupby

이번 강의에서는 .groupby를 이용해 각 열의 값을 기준으로 데이터를 보는 방법을 살펴보자.

import pandas as pd
  
df = pd.read_csv('adult.csv', skipinitialspace=True, header=None)  # 값 앞에 공백이 포함되어 있기 때문에 제거해야 함
  
titles = {0: '나이', 1: '고용형태', 2: '가중치',
            3: '최종학위', 4:'교육년수', 5: '결혼유무',
            6: '직업명', 7:'가족내역할', 8:'인종',
            9: '성별', 10: '자본이득', 11: '자본손실',
            12: '근무시간', 13:'고향', 14:'수입'
           }

  df.rename(columns=titles, inplace=True)
  
df.head()
  
나이 고용형태 가중치 최종학위 교육년수 결혼유무 직업명 가족내역할 인종 성별 자본이득 자본손실 근무시간 고향 수입
0 39 State-gov 77516 Bachelors 13 Never-married Adm-clerical Not-in-family White Male 2174 0 40 United-States <=50K
1 50 Self-emp-not-inc 83311 Bachelors 13 Married-civ-spouse Exec-managerial Husband White Male 0 0 13 United-States <=50K
2 38 Private 215646 HS-grad 9 Divorced Handlers-cleaners Not-in-family White Male 0 0 40 United-States <=50K
3 53 Private 234721 11th 7 Married-civ-spouse Handlers-cleaners Husband Black Male 0 0 40 United-States <=50K
4 28 Private 338409 Bachelors 13 Married-civ-spouse Prof-specialty Wife Black Female 0 0 40 Cuba <=50K

가족내 역할이 '남편' 인 자료 색인

groupby 는 선택한 열의 그룹별로 자료를 분류한다.

예를 들어보기 위해 '가족내역할' 열의 값들을 살펴보자.

df['가족내역할'].unique()
  
array(['Not-in-family', 'Husband', 'Wife', 'Own-child', 'Unmarried',
         'Other-relative'], dtype=object)

가족이 없는 사람, 남편, 부인, 자식, 미혼 등등의 값을 갖는다.

groupby

groupby 를 적용시켜보자.

df.groupby('가족내역할')
  
<pandas.core.groupby.DataFrameGroupBy object at 0x0000012DAE0CB780>

아직까지는 자료를 볼 수 없다

이를 grouped에 저장하자.

grouped = df.groupby('가족내역할')
  

.groups로 전체 그룹과 데이터 프레임에서 위치를 살펴 볼 수 있다. 결과물은 사전과 같은 형태로 나온다.

grouped.groups
  
{'Husband': Int64Index([    1,     3,     7,     9,    10,    11,    14,    15,    18,
                  20,
               ...
               32532, 32533, 32539, 32542, 32547, 32550, 32551, 32552, 32554,
               32557],
              dtype='int64', length=13193),
   'Not-in-family': Int64Index([    0,     2,     6,     8,    13,    28,    30,    44,    49,
                  53,
               ...
               32523, 32531, 32536, 32537, 32541, 32544, 32546, 32548, 32553,
               32555],
              dtype='int64', length=8305),
   'Other-relative': Int64Index([   74,   110,   144,   152,   159,   195,   198,   233,   317,
                 335,
               ...
               32306, 32322, 32359, 32387, 32398, 32401, 32477, 32499, 32524,
               32549],
              dtype='int64', length=981),
   'Own-child': Int64Index([   12,    16,    26,    31,    32,    33,    36,    51,    69,
                  70,
               ...
               32486, 32488, 32492, 32496, 32511, 32512, 32527, 32535, 32540,
               32559],
              dtype='int64', length=5068),
   'Unmarried': Int64Index([   17,    19,    21,    24,    35,    43,    47,    91,    92,
                  98,
               ...
               32493, 32500, 32516, 32520, 32525, 32529, 32534, 32538, 32543,
               32558],
              dtype='int64', length=3446),
   'Wife': Int64Index([    4,     5,    37,    50,    52,    67,    82,    93,   113,
                 125,
               ...
               32425, 32428, 32479, 32485, 32513, 32528, 32530, 32545, 32556,
               32560],
              dtype='int64', length=1568)}

이중에서 Husband 를 색인해보자.

grouped.groups['Husband']
  
Int64Index([    1,     3,     7,     9,    10,    11,    14,    15,    18,
                 20,
              ...
              32532, 32533, 32539, 32542, 32547, 32550, 32551, 32552, 32554,
              32557],
             dtype='int64', length=13193)

1, 3, 7, 인덱스를 가진 행들이 '가족내역할' 중에서 Husband 의 값을 가진다는 것을 알 수 있다.

husband 의 수

Husband 의 값을 가지는 값이 몇개인지 알아보자.

len(df[df['가족내역할'] == 'Husband'])
  
13193

자료가 어떻게 생겼는지 감을 잡기 위해, .first() 로 각 그룹마다 첫 자료를 열람해보자.

grouped.first()
  
나이 고용형태 가중치 최종학위 교육년수 결혼유무 직업명 인종 성별 자본이득 자본손실 근무시간 고향 수입
가족내역할
Husband 50 Self-emp-not-inc 83311 Bachelors 13 Married-civ-spouse Exec-managerial White Male 0 0 13 United-States <=50K
Not-in-family 39 State-gov 77516 Bachelors 13 Never-married Adm-clerical White Male 2174 0 40 United-States <=50K
Other-relative 79 Private 124744 Some-college 10 Married-civ-spouse Prof-specialty White Male 0 0 20 United-States <=50K
Own-child 23 Private 122272 Bachelors 13 Never-married Adm-clerical White Female 0 0 30 United-States <=50K
Unmarried 32 Private 186824 HS-grad 9 Never-married Machine-op-inspct White Male 0 0 40 United-States <=50K
Wife 28 Private 338409 Bachelors 13 Married-civ-spouse Prof-specialty Black Female 0 0 40 Cuba <=50K

.keys()로 키 값을 확인해보자.

grouped.groups.keys()
  
dict_keys(['Husband', 'Not-in-family', 'Other-relative', 'Own-child', 'Unmarried', 'Wife'])

이 값들은, .unique()의 값과 같다.

set(df['가족내역할'].unique()) == set(grouped.groups.keys())
  
True

즉, '가족내역할'의 값 그룹이 key 로 grouped 에 들어가있다.

계산

그룹 바이로 그룹별로 평균, 합 등을 구할 수 있다.

mean

가족 구성원의 나이 평균을 구해보자.

grouped['나이'].mean()
  
가족내역할
  Husband           43.818616
  Not-in-family     38.346057
  Other-relative    33.164118
  Own-child         24.827940
  Unmarried         40.293964
  Wife              39.846301
  Name: 나이, dtype: float64

확인 결과, 남편들은 평균 43 세, 아내들은 평균 39 세 입니다.

sum

그룹별 총 자본이득을 구해보자.

grouped['자본이득'].sum()
  
가족내역할
  Husband           23682256
  Not-in-family      6173333
  Other-relative      274283
  Own-child           788862
  Unmarried          1568037
  Wife               2602553
  Name: 자본이득, dtype: int64

확인결과, 남편의 자본이득이 가장 높다.

count

.count()로 그룹별 출현 수를 얻을 수 있다. 이때 어느 열을 사용하든, 같은 결과를 얻을 수 있다. 임시로 나이의 count 를 구해보자.

grouped['나이'].count()
  
가족내역할
  Husband           13193
  Not-in-family      8305
  Other-relative      981
  Own-child          5068
  Unmarried          3446
  Wife               1568
  Name: 나이, dtype: int64

이 자료에는 남편인 사람들이 가장 많다.

sort_values

count 결과를 정렬해보자.

grouped['나이'].count().sort_values()
  
가족내역할
  Other-relative      981
  Wife               1568
  Unmarried          3446
  Own-child          5068
  Not-in-family      8305
  Husband           13193
  Name: 나이, dtype: int64

내림차순으로 정렬해보자.

grouped['나이'].count().sort_values(ascending=False)
  
가족내역할
  Husband           13193
  Not-in-family      8305
  Own-child          5068
  Unmarried          3446
  Wife               1568
  Other-relative      981
  Name: 나이, dtype: int64
describe

.describe()로 자료의 요약을 살펴볼 수 있다.

grouped['나이'].describe()
  
count mean std min 25% 50% 75% max
가족내역할
Husband 13193.0 43.818616 12.024349 17.0 35.0 43.0 52.0 90.0
Not-in-family 8305.0 38.346057 13.865379 17.0 27.0 35.0 47.0 90.0
Other-relative 981.0 33.164118 14.166235 17.0 22.0 28.0 41.0 90.0
Own-child 5068.0 24.827940 8.115799 17.0 19.0 22.0 27.0 90.0
Unmarried 3446.0 40.293964 11.605904 17.0 32.0 39.0 47.0 90.0
Wife 1568.0 39.846301 11.227700 18.0 31.0 39.0 47.0 90.0

get_group

하나의 그룹만을 선택하고 싶다면, .get_group()을 사용하면 된다.

grouped.get_group('Not-in-family').head()
  
가중치 결혼유무 고용형태 고향 교육년수 근무시간 나이 성별 수입 인종 자본손실 자본이득 직업명 최종학위
0 77516 Never-married State-gov United-States 13 40 39 Male <=50K White 0 2174 Adm-clerical Bachelors
2 215646 Divorced Private United-States 9 40 38 Male <=50K White 0 0 Handlers-cleaners HS-grad
6 160187 Married-spouse-absent Private Jamaica 5 16 49 Female <=50K Black 0 0 Other-service 9th
8 45781 Never-married Private United-States 14 50 31 Female >50K White 0 14084 Prof-specialty Masters
13 205019 Never-married Private United-States 12 50 32 Male <=50K Black 0 0 Sales Assoc-acdm

그룹 바이 한 결과를, for문으로 돌릴 수 있다.

for name, group in grouped:  # grouped 는 그룹별로 key 와 value 로 구성되어있는 사전
      print(name)  # key 값
      print(group['근무시간'].sum())  # 주별 근무시간의 합
  

그룹마다 근무시간의 합이 출력된다.

12.2.2. group by - 열 두 개 이상 사용하기

groupby 를 사용해 그룹내에 그룹을 또 나눌 수 있다.

예제로 adult.csv 를 사용해보자.

import pandas as pd
  
df = pd.read_csv('adult.csv', skipinitialspace=True, header=None)  # 값 앞에 공백이 포함 되어있기 때문에 제거
  
titles = {0: '나이', 1: '고용형태', 2: '가중치',
            3: '최종학위', 4:'교육년수', 5: '결혼유무',
            6: '직업명', 7:'가족내역할', 8:'인종',
            9: '성별', 10: '자본이득', 11: '자본손실',
            12: '근무시간', 13:'고향', 14:'수입'
           }

  df.rename(columns=titles, inplace=True)
  
df.head()
  
나이 고용형태 가중치 최종학위 교육년수 결혼유무 직업명 가족내역할 인종 성별 자본이득 자본손실 근무시간 고향 수입
0 39 State-gov 77516 Bachelors 13 Never-married Adm-clerical Not-in-family White Male 2174 0 40 United-States <=50K
1 50 Self-emp-not-inc 83311 Bachelors 13 Married-civ-spouse Exec-managerial Husband White Male 0 0 13 United-States <=50K
2 38 Private 215646 HS-grad 9 Divorced Handlers-cleaners Not-in-family White Male 0 0 40 United-States <=50K
3 53 Private 234721 11th 7 Married-civ-spouse Handlers-cleaners Husband Black Male 0 0 40 United-States <=50K
4 28 Private 338409 Bachelors 13 Married-civ-spouse Prof-specialty Wife Black Female 0 0 40 Cuba <=50K

다중 그룹

이번에는 두개의 열을 .groupby() 에 리스트 형태로 넣어보자.

grouped = df.groupby(['가족내역할', '인종'])
  
grouped
  
<pandas.core.groupby.DataFrameGroupBy object at 0x000001D589EF7908>

그룹을 확인해보자

grouped.first()
  
나이 고용형태 가중치 최종학위 교육년수 결혼유무 직업명 성별 자본이득 자본손실 근무시간 고향 수입
가족내역할 인종
Husband Amer-Indian-Eskimo 34 Private 245487 7th-8th 4 Married-civ-spouse Transport-moving Male 0 0 45 Mexico <=50K
Asian-Pac-Islander 30 State-gov 141297 Bachelors 13 Married-civ-spouse Prof-specialty Male 0 0 40 India >50K
Black 53 Private 234721 11th 7 Married-civ-spouse Handlers-cleaners Male 0 0 40 United-States <=50K
Other 28 Private 166481 7th-8th 4 Married-civ-spouse Handlers-cleaners Male 0 2179 40 Puerto-Rico <=50K
White 50 Self-emp-not-inc 83311 Bachelors 13 Married-civ-spouse Exec-managerial Male 0 0 13 United-States <=50K
Not-in-family Amer-Indian-Eskimo 35 Private 153790 Some-college 10 Never-married Sales Female 0 0 40 United-States <=50K
Asian-Pac-Islander 28 Private 88419 HS-grad 9 Never-married Exec-managerial Female 0 0 40 England <=50K
Black 49 Private 160187 9th 5 Married-spouse-absent Other-service Female 0 0 16 Jamaica <=50K
Other 40 Private 237601 Bachelors 13 Never-married Sales Female 0 0 55 United-States >50K
White 39 State-gov 77516 Bachelors 13 Never-married Adm-clerical Male 2174 0 40 United-States <=50K
Other-relative Amer-Indian-Eskimo 27 Private 23940 HS-grad 9 Never-married Handlers-cleaners Male 0 0 40 United-States <=50K
Asian-Pac-Islander 33 Private 163003 Bachelors 13 Never-married Exec-managerial Female 0 0 40 Philippines <=50K
Black 42 Private 228456 Bachelors 13 Separated Other-service Male 0 0 50 United-States <=50K
Other 33 Private 110978 Some-college 10 Divorced Craft-repair Female 0 0 40 United-States <=50K
White 79 Private 124744 Some-college 10 Married-civ-spouse Prof-specialty Male 0 0 20 United-States <=50K
Own-child Amer-Indian-Eskimo 20 Private 27337 HS-grad 9 Never-married Handlers-cleaners Male 0 0 48 United-States <=50K
Asian-Pac-Islander 27 Private 116358 Some-college 10 Never-married Craft-repair Male 0 1980 40 Philippines <=50K
Black 20 Private 266015 Some-college 10 Never-married Sales Male 0 0 44 United-States <=50K
Other 23 State-gov 123983 Bachelors 13 Never-married Protective-serv Male 0 0 40 United-States <=50K
White 23 Private 122272 Bachelors 13 Never-married Adm-clerical Female 0 0 30 United-States <=50K
Unmarried Amer-Indian-Eskimo 23 Private 99399 Some-college 10 Never-married Other-service Female 0 0 25 United-States <=50K
Asian-Pac-Islander 44 Self-emp-inc 78374 Masters 14 Divorced Exec-managerial Female 0 0 40 United-States <=50K
Black 54 Private 302146 HS-grad 9 Separated Other-service Female 0 0 20 United-States <=50K
Other 65 Private 161400 11th 7 Widowed Other-service Male 0 0 40 United-States <=50K
White 32 Private 186824 HS-grad 9 Never-married Machine-op-inspct Male 0 0 40 United-States <=50K
Wife Amer-Indian-Eskimo 29 Private 100405 10th 6 Married-civ-spouse Farming-fishing Female 0 0 40 United-States <=50K
Asian-Pac-Islander 30 Private 117747 HS-grad 9 Married-civ-spouse Sales Female 0 1573 35 Laos <=50K
Black 28 Private 338409 Bachelors 13 Married-civ-spouse Prof-specialty Female 0 0 40 Cuba <=50K
Other 25 Private 32275 Some-college 10 Married-civ-spouse Exec-managerial Female 0 0 40 United-States <=50K
White 37 Private 284582 Masters 14 Married-civ-spouse Exec-managerial Female 0 0 40 United-States <=50K

이번에는 가족내역할 안에서 인종으로 또 그룹이 나뉘었다.

groupby 계산

그룹이 하나였을때와 마찬가지로, 자료의 평균, 합, 카운트 등을 얻을 수 있다.

mean

가족내역할과 인종별로 평균 교육햇수를 구해보자.

grouped['교육년수'].mean()
  
가족내역할           인종
  Husband         Amer-Indian-Eskimo     9.250000
                  Asian-Pac-Islander    11.565854
                  Black                  9.667660
                  Other                  8.775000
                  White                 10.339615
  Not-in-family   Amer-Indian-Eskimo     9.432099
                  Asian-Pac-Islander    11.046729
                  Black                  9.653941
                  Other                  9.534247
                  White                 10.387088
  Other-relative  Amer-Indian-Eskimo     8.769231
                  Asian-Pac-Islander     9.914634
                  Black                  8.902439
                  Other                  8.750000
                  White                  8.628242
  Own-child       Amer-Indian-Eskimo     9.145833
                  Asian-Pac-Islander    10.231214
                  Black                  9.266667
                  Other                  8.810811
                  White                  9.494242
  Unmarried       Amer-Indian-Eskimo     9.224138
                  Asian-Pac-Islander    10.967033
                  Black                  9.349805
                  Other                  7.675676
                  White                  9.723003
  Wife            Amer-Indian-Eskimo    10.157895
                  Asian-Pac-Islander    10.159420
                  Black                  9.908497
                  Other                  8.937500
                  White                 10.566743
  Name: 교육년수, dtype: float64

남편인데 인종이 에스키모일 경우 평균 교육기간은 9년이지만, 아내인데 인종이 에스키모일 경우 평균 교육기간은 10년이다.

sum

이번에는 노동시간의 합을 구해보자.

grouped['근무시간'].sum()
  
가족내역할           인종
  Husband         Amer-Indian-Eskimo      3935
                  Asian-Pac-Islander     17835
                  Black                  28156
                  Other                   3449
                  White                 528702
  Not-in-family   Amer-Indian-Eskimo      3352
                  Asian-Pac-Islander      8421
                  Black                  32109
                  Other                   2938
                  White                 290327
  Other-relative  Amer-Indian-Eskimo       512
                  Asian-Pac-Islander      3014
                  Black                   6007
                  Other                   1053
                  White                  25717
  Own-child       Amer-Indian-Eskimo      1662
                  Asian-Pac-Islander      6094
                  Black                  18940
                  Other                   1271
                  White                 140642
  Unmarried       Amer-Indian-Eskimo      2234
                  Asian-Pac-Islander      3632
                  Black                  29118
                  Other                   1403
                  White                  98362
  Wife            Amer-Indian-Eskimo       760
                  Asian-Pac-Islander      2696
                  Black                   5703
                  Other                    582
                  White                  48058
  Name: 근무시간, dtype: int64
sort values

값을 정렬해서 보고싶다면, .sort_values() 를 적용할 수 있다.

grouped['근무시간'].sum().sort_values()
  
가족내역할           인종
  Other-relative  Amer-Indian-Eskimo       512
  Wife            Other                    582
                  Amer-Indian-Eskimo       760
  Other-relative  Other                   1053
  Own-child       Other                   1271
  Unmarried       Other                   1403
  Own-child       Amer-Indian-Eskimo      1662
  Unmarried       Amer-Indian-Eskimo      2234
  Wife            Asian-Pac-Islander      2696
  Not-in-family   Other                   2938
  Other-relative  Asian-Pac-Islander      3014
  Not-in-family   Amer-Indian-Eskimo      3352
  Husband         Other                   3449
  Unmarried       Asian-Pac-Islander      3632
  Husband         Amer-Indian-Eskimo      3935
  Wife            Black                   5703
  Other-relative  Black                   6007
  Own-child       Asian-Pac-Islander      6094
  Not-in-family   Asian-Pac-Islander      8421
  Husband         Asian-Pac-Islander     17835
  Own-child       Black                  18940
  Other-relative  White                  25717
  Husband         Black                  28156
  Unmarried       Black                  29118
  Not-in-family   Black                  32109
  Wife            White                  48058
  Unmarried       White                  98362
  Own-child       White                 140642
  Not-in-family   White                 290327
  Husband         White                 528702
  Name: 근무시간, dtype: int64

가족내역할과 상관없이 백인이 총 노동시간이 큰것으로 보여진다.

count

각 그룹마다 자료가 몇개있는지 보고싶다면 .count() 를 사용할 수 있다. 이때, 자료에 결측값이 없다면 어떤 열을 사용해도 같은 값을 얻는다.

grouped['고향'].count()
  
가족내역할           인종
  Husband         Amer-Indian-Eskimo       92
                  Asian-Pac-Islander      369
                  Black                   647
                  Other                    75
                  White                 11764
  Not-in-family   Amer-Indian-Eskimo       81
                  Asian-Pac-Islander      202
                  Black                   782
                  Other                    66
                  White                  7025
  Other-relative  Amer-Indian-Eskimo       13
                  Asian-Pac-Islander       73
                  Black                   155
                  Other                    27
                  White                   684
  Own-child       Amer-Indian-Eskimo       48
                  Asian-Pac-Islander      166
                  Black                   546
                  Other                    35
                  White                  4210
  Unmarried       Amer-Indian-Eskimo       58
                  Asian-Pac-Islander       86
                  Black                   749
                  Other                    36
                  White                  2455
  Wife            Amer-Indian-Eskimo       19
                  Asian-Pac-Islander       60
                  Black                   149
                  Other                    14
                  White                  1292
  Name: 고향, dtype: int64

백인 남편에 대한 자료가 가장 많다.

숫자 자료형

지금까지는 그룹이 문자열로 표현된 경우만 살펴봤다. 이번에는 숫자 자료형을 groupby 해보자. 숫자 자료형을 groupby 하면, 각 숫자가 key 가 되어 그룹이 만들어진다.

예시로 문자열 자료형인 '성별'과 숫자 자료형인 '교육햇수'를 groupby 해보자.

grouped2 = df.groupby(['성별', '교육년수'])
  
grouped2.first()
  
나이 고용형태 가중치 최종학위 결혼유무 직업명 가족내역할 인종 자본이득 자본손실 근무시간 고향 수입
성별 교육년수
Female 1 53 Local-gov 140359 Preschool Never-married Machine-op-inspct Not-in-family White 0 0 35 United-States <=50K
2 68 Private 38317 1st-4th Divorced Priv-house-serv Not-in-family White 0 0 20 United-States <=50K
3 22 Private 399087 5th-6th Married-civ-spouse Machine-op-inspct Other-relative White 0 0 40 Mexico <=50K
4 45 Private 433665 7th-8th Separated Other-service Unmarried White 0 0 40 Mexico <=50K
5 49 Private 160187 9th Married-spouse-absent Other-service Not-in-family Black 0 0 16 Jamaica <=50K
6 17 Local-gov 304873 10th Never-married Other-service Own-child White 34095 0 32 United-States <=50K
7 18 Private 309634 11th Never-married Other-service Own-child White 0 0 22 United-States <=50K
8 58 Self-emp-not-inc 211547 12th Divorced Sales Not-in-family White 0 0 52 United-States <=50K
9 54 Private 302146 HS-grad Separated Other-service Unmarried Black 0 0 20 United-States <=50K
10 25 Private 32275 Some-college Married-civ-spouse Exec-managerial Wife Other 0 0 40 United-States <=50K
11 27 Private 163127 Assoc-voc Married-civ-spouse Adm-clerical Wife White 0 0 35 United-States <=50K
12 48 Private 171095 Assoc-acdm Divorced Exec-managerial Unmarried White 0 0 40 England <=50K
13 28 Private 338409 Bachelors Married-civ-spouse Prof-specialty Wife Black 0 0 40 Cuba <=50K
14 37 Private 284582 Masters Married-civ-spouse Exec-managerial Wife White 0 0 40 United-States <=50K
15 47 Private 51835 Prof-school Married-civ-spouse Prof-specialty Wife White 0 1902 60 Honduras >50K
16 43 Federal-gov 410867 Doctorate Never-married Prof-specialty Not-in-family White 0 0 50 United-States >50K
Male 1 51 Local-gov 241843 Preschool Married-civ-spouse Other-service Husband White 0 0 40 United-States <=50K
2 64 Private 187656 1st-4th Divorced Machine-op-inspct Not-in-family White 0 0 40 United-States <=50K
3 46 Private 216666 5th-6th Married-civ-spouse Machine-op-inspct Husband White 0 0 40 Mexico <=50K
4 34 Private 245487 7th-8th Married-civ-spouse Transport-moving Husband Amer-Indian-Eskimo 0 0 45 Mexico <=50K
5 35 Federal-gov 76845 9th Married-civ-spouse Farming-fishing Husband Black 0 0 40 United-States <=50K
6 67 Private 212759 10th Married-civ-spouse Craft-repair Husband White 0 0 2 United-States <=50K
7 53 Private 234721 11th Married-civ-spouse Handlers-cleaners Husband Black 0 0 40 United-States <=50K
8 35 Private 92440 12th Divorced Craft-repair Not-in-family White 0 0 50 United-States >50K
9 38 Private 215646 HS-grad Divorced Handlers-cleaners Not-in-family White 0 0 40 United-States <=50K
10 37 Private 280464 Some-college Married-civ-spouse Exec-managerial Husband Black 0 0 80 United-States >50K
11 40 Private 121772 Assoc-voc Married-civ-spouse Craft-repair Husband Asian-Pac-Islander 0 0 40 United-States >50K
12 32 Private 205019 Assoc-acdm Never-married Sales Not-in-family Black 0 0 50 United-States <=50K
13 39 State-gov 77516 Bachelors Never-married Adm-clerical Not-in-family White 2174 0 40 United-States <=50K
14 33 Private 202051 Masters Married-civ-spouse Prof-specialty Husband White 0 0 50 United-States <=50K
15 38 Private 65324 Prof-school Married-civ-spouse Prof-specialty Husband White 0 0 40 United-States >50K
16 40 Private 193524 Doctorate Married-civ-spouse Prof-specialty Husband White 0 0 60 United-States >50K

교육햇수가 1부터 16까지 그룹지어졌다.

mean & sort

grouped2 의 평균 나이를 정렬해서 살펴보자.

grouped2['근무시간'].mean().sort_values()
  
성별      교육년수
  Female  7       29.821759
          8       31.791667
          1       31.875000
          2       31.978261
          6       32.111864
          5       33.916667
          10      34.574840
          3       36.047619
          4       36.200000
  Male    7       36.312248
  Female  9       36.577286
          12      37.358670
  Male    8       37.768166
  Female  11      37.830000
  Male    1       38.828571
  Female  13      39.329216
  Male    6       39.336991
          5       39.651351
          3       39.859438
          4       40.409465
          2       40.622951
  Female  14      41.113806
  Male    10      41.528428
          9       42.481367
          12      42.554180
          11      43.753968
          13      44.037473
  Female  15      44.793478
  Male    14      45.065712
          16      46.886850
  Female  16      47.302326
  Male    15      47.925620
  Name: 근무시간, dtype: float64

교육햇수가 15, 16 일때, 성별 상관없이 평균 노동시간이 많은것을 알 수 있다.

데이터 프레임으로 보기

지금까지는 자료를 Series 형태로 봤다. [[]] 를 사용하면, 이를 데이터 프레임으로 바꿀 수 있다.

grouped2[['근무시간']].mean().head()
  
근무시간
성별 교육년수
Female 1 31.875000
2 31.978261
3 36.047619
4 36.200000
5 33.916667

level 로 접근

여러 그룹으로 groupby 를 했지만, 한 그룹만 선택하고 싶다면, .groupby(level=0) 으로 접근할 수 있다.

예를 들어, 성별과 교육년수별로 근무시간 합을 구한후, 그 합들의 그룹별 평균을 구할 수 있다.

먼저 성별과 교육년수별 근무시간의 합을 구해보자.

grouped2['근무시간'].sum()
  
성별      교육년수
  Female  1          510
          2         1471
          3         3028
          4         5792
          5         4884
          6         9473
          7        12883
          8         4578
          9       123997
          10       97017
          11       18915
          12       15728
          13       63674
          14       22037
          15        4121
          16        4068
  Male    1         1359
          2         4956
          3         9925
          4        19639
          5        14671
          6        25097
          7        26980
          8        10915
          9       302085
          10      186255
          11       38591
          12       27490
          13      164524
          14       53493
          15       23196
          16       15332
  Name: 근무시간, dtype: int64
level=0

다음으로는 총 근무시간을 성별 평균으로 보자.

뒤에 .groupby(level=0).mean() 를 붙이면 성별 평균을 볼 수 있다.

grouped2['근무시간'].sum().groupby(level=0).mean()
  
성별
  Female    24511.00
  Male      57781.75
  Name: 근무시간, dtype: float64
level=1

만약 총 근무시간을 교육년수 평균으로 보고싶다면, level=1 라고 바꿔주면 된다.

grouped2['근무시간'].sum().groupby(level=1).mean()
  
교육년수
  1        934.5
  2       3213.5
  3       6476.5
  4      12715.5
  5       9777.5
  6      17285.0
  7      19931.5
  8       7746.5
  9     213041.0
  10    141636.0
  11     28753.0
  12     21609.0
  13    114099.0
  14     37765.0
  15     13658.5
  16      9700.0
  Name: 근무시간, dtype: float64

모양 풀기

reset index

.reset_index()로 다중 인덱스를 풀 수 있다.

grouped2['근무시간'].mean().reset_index().head()
  
성별 교육년수 근무시간
0 Female 1 31.875000
1 Female 2 31.978261
2 Female 3 36.047619
3 Female 4 36.200000
4 Female 5 33.916667
unstack

.unstack()로 다중 인덱스를 가로와 세로축으로 나타낼 수 있다.

grouped2['근무시간'].mean().unstack()
  
교육년수 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
성별
Female 31.875000 31.978261 36.047619 36.200000 33.916667 32.111864 29.821759 31.791667 36.577286 34.574840 37.830000 37.35867 39.329216 41.113806 44.793478 47.302326
Male 38.828571 40.622951 39.859438 40.409465 39.651351 39.336991 36.312248 37.768166 42.481367 41.528428 43.753968 42.55418 44.037473 45.065712 47.925620 46.886850

12.2.3. apply

.apply().map() 등을 이용해 데이터 프레임의 열, 행, 값에 함수를 적용할 수 있다.

예제로 산불 데이터를 사용하자.

import pandas as pd
  df = pd.read_excel('forestfires.xlsx')
  df.head()
  
X Y month day FFMC DMC DC ISI temp RH wind rain area
0 7 5 mar fri 86.2 26.2 94.3 5.1 8.2 51 6.7 0.0 0.0
1 7 4 oct tue 90.6 35.4 669.1 6.7 18.0 33 0.9 0.0 0.0
2 7 4 oct sat 90.6 43.7 686.9 6.7 14.6 33 1.3 0.0 0.0
3 8 6 mar fri 91.7 33.3 77.5 9.0 8.3 97 4.0 0.2 0.0
4 8 6 mar sun 89.3 51.3 102.2 9.6 11.4 99 1.8 0.0 0.0

함수 소개

판다스에서 함수를 일괄 적용하는 방법에 크게 세가지 방식이 있다.

Series 에서 사용할 수 있는 함수

map

.map() 은 Series 에 적용할 수 있다.

먼저, ISI 열을 불러와 연습해보자.

s = df['ISI']
  s.head()
  
0    5.1
  1    6.7
  2    6.7
  3    9.0
  4    9.6
  Name: ISI, dtype: float64

s 는 Series 다.

함수 만들기

이젠, 적용시킬 함수를 만들어보자.

값을 10으로 나누는 div_10 함수를 만들어보자.

def div_10(x):
      return x / 10
  

잘 작동하는지 확인해보자.

div_10(30)
  
3.0

함수가 문제없이 실행된다.

함수 적용

다음으로는 함수를 .map() 안에 넘겨줘 s 에 적용해보자.

s.map(div_10).head()
  
0    0.51
  1    0.67
  2    0.67
  3    0.90
  4    0.96
  Name: ISI, dtype: float64

div_10 함수가 적용된 값이 반환되었다.

lambda 사용

같은 코드를 파이썬의 lambda 함수로 만들 수 있다.

람다 함수에 대한 자세한 설명은 별도 강의에서 찾아볼 수 있다.

x 마다 10 을 나누는 람다 함수를 .map() 에 넘겨준다.

s.map(lambda x: x / 10).head()
  
0    0.51
  1    0.67
  2    0.67
  3    0.90
  4    0.96
  Name: ISI, dtype: float64

같은 결과를 얻을 수 있다.

Data Frame

apply

이번에는 데이터 프레임에 적용할 수 있는 함수들을 살펴보자.

apply 는 각 값을 함수의 인수로 넘기는 것이 아니라, 열 혹은 행을 통으로 함수의 인수로 넘긴다.

axis=0

apply 의 작동방식을 이해해보자. 먼저, 초기값인 axis=0 인 경우를 살펴보자. axis=0 일때 데이터 프레임의 열 단위로 함수를 적용한다.

df.iteritems() 는 데이터 프레임의 열 이름과 열 값을 제공하는 generator 이다. 예시를 보이기 위해 for 문으로 첫 열의 이름과 열의 첫 다섯값만 프린트해보자.

for column_name, data in df.iteritems():
      print(column_name)
      print(data.head())
      break
  

data 는 Series 형태임을 알 수 있다.

실제로 apply 에 넘겨지는 함수는 Series 인 data 를 인수로 받는다.

확인해보기 위해, 인수를 프린트 하는 함수 see_x 를 만들어 apply 에 넣어보자.

def see_x(x):
      print(x.head())
  

간략하게 보기 위해 wind, rain, area 열에만 적용해보자.

df[['wind', 'rain', 'area']].apply(see_x)
  
wind    None
  rain    None
  area    None
  dtype: object

이 원리를 기반으로 apply 에 넣을 함수를 만들 수 있다.

예시

이번에는 apply 에 see_x 함수 대신, Series 의 최대값에서 최소값을 빼는 max_min 함수를 적용해보자.

먼저 max_min 함수를 만든다. .max().min() 으로 최대, 최소값을 구한다.

def max_min(x):
      return x.max() - x.min()
  

수치 자료인 wind, rain, area 에만 적용해보자.

df[['wind', 'rain', 'area']].apply(max_min)
  
wind       9.00
  rain       6.40
  area    1090.84
  dtype: float64

max_min 함수가 데이터 프레임에 적용되었다.

axis = 1

이번에는 axis=1 일 경우를 살펴보자.

axis=1 일때 작동방식을 살펴보기 위해 .iterrows() 를 살펴보자. .iterrows().iteritems()와 반대로 각 행의 위치와 행 정보를 generator 로 만들어준다.

for i, item in df.iterrows():  # 위치, 행 정보
      print(i)
      print(item)
      break
  

첫 행의 행정보가 들어온것을 볼 수 있다.

위에서 만든 see_x 함수를 apply 에 적용해보자.

간략하게 보기 위해 첫 다섯 행만 살펴보자.

df.iloc[:5].apply(see_x, axis=1)
  
0    None
  1    None
  2    None
  3    None
  4    None
  dtype: object

각 행이 함수의 인수로 넘겨진 것을 볼 수 있다.

예시

이번에도 min_max 함수를 수치 자료형인 wind, rain, area 열에 적용해보자. 간략하게 보기 위해 첫 다섯 행에만 함수를 적용해보자.

df[['wind', 'rain', 'area']].iloc[:5].apply(max_min, axis=1)
  
0    6.7
  1    0.9
  2    1.3
  3    4.0
  4    1.8
  dtype: float64

이번에는 행마다 wind, rain, area 중에서 최대값과 최소값의 차가 구해졌다.

applymap

applymap 은 데이터 프레임의 각 값에 함수를 적용한다.

예시로 wind, rain, area 에 '%' 표시를 붙여보자.

현재 wind, rain, area 에는 '%' 표시가 없다.

df[['wind', 'rain', 'area']].head()
  
wind rain area
0 6.7 0.0 0.0
1 0.9 0.0 0.0
2 1.3 0.0 0.0
3 4.0 0.2 0.0
4 1.8 0.0 0.0

먼저 퍼센트 표시를 붙이는 함수 add_percent 를 만든다.

def add_percent(x):
      return str(x) + '%'
  

함수가 잘 작동하는지 확인한다

add_percent(99)
  
'99%'

다음으로는 wind, rain, area 에 .applymap 을 사용해 add_percent 함수를 적용한다.

df[['wind', 'rain', 'area']].applymap(add_percent).head()
  
wind rain area
0 6.7% 0.0% 0.0%
1 0.9% 0.0% 0.0%
2 1.3% 0.0% 0.0%
3 4.0% 0.2% 0.0%
4 1.8% 0.0% 0.0%

원하는대로 자료가 바뀌었다.

lambda

같은 함수를 람다로 표현할 수 있다.

df[['wind', 'rain', 'area']].applymap(lambda x: str(x) + '%').head()
  
wind rain area
0 6.7% 0.0% 0.0%
1 0.9% 0.0% 0.0%
2 1.3% 0.0% 0.0%
3 4.0% 0.2% 0.0%
4 1.8% 0.0% 0.0%

12.2.4. agg

agg 는 그룹 연산을 위한 메써드이다.

각 열에 적용하고 싶은 함수가 있을 때, agg 를 사용한다.

예시로 산불 데이터를 사용하자.

import pandas as pd
  
df = pd.read_excel('forestfires.xlsx')
  
df.head()
  
X Y month day FFMC DMC DC ISI temp RH wind rain area
0 7 5 mar fri 86.2 26.2 94.3 5.1 8.2 51 6.7 0.0 0.0
1 7 4 oct tue 90.6 35.4 669.1 6.7 18.0 33 0.9 0.0 0.0
2 7 4 oct sat 90.6 43.7 686.9 6.7 14.6 33 1.3 0.0 0.0
3 8 6 mar fri 91.7 33.3 77.5 9.0 8.3 97 4.0 0.2 0.0
4 8 6 mar sun 89.3 51.3 102.2 9.6 11.4 99 1.8 0.0 0.0

하나의 함수 적용

.agg()max 함수를 넘겨, 각 열의 최대값을 구해보자.

df.agg(max)
  
X              9
  Y              9
  month        sep
  day          wed
  FFMC        96.2
  DMC        291.3
  DC         860.6
  ISI         56.1
  temp        33.3
  RH           100
  wind         9.4
  rain         6.4
  area     1090.84
  dtype: object

열 별 최대값이 구해졌다.

month와 day 같은 경우, 값이 문자열일때는 알파벳 순서로 가장 뒤에 있는 값이 구해졌다.

문자열 표현법

문자열로 'max' 를 넘겨줘도 같은 결과를 구한다.

df.agg('max')
  
X              9
  Y              9
  month        sep
  day          wed
  FFMC        96.2
  DMC        291.3
  DC         860.6
  ISI         56.1
  temp        33.3
  RH           100
  wind         9.4
  rain         6.4
  area     1090.84
  dtype: object

편의를 위해 연속변수들만 따로 모아 forest 라고 저장하자.

forest = df[['X', 'Y', 'temp', 'wind', 'rain', 'area']]
  

여러 함수 적용

이번에는 sum, min, max 함수를 .agg() 에 리스트 형태로 넘겨보자.

forest.agg([sum, min, max])
  
X Y temp wind rain area
sum 2414 2223 9765.7 2077.1 11.2 6642.05
min 1 2 2.2 0.4 0.0 0.00
max 9 9 33.3 9.4 6.4 1090.84

세 함수가 모든 열에 적용되었다.

열마다 다르게 지정

열마다 다른 함수를 적용하게 지정할 수 있다. 열마다 다른 함수를 지정하고 싶다면, 열 이름과 적용하고 싶은 함수를 짝지어 사전으로 넘겨준다.

최대 온도와 최소 강수량을 구하도록 사전을 만들어 넘겨보자.

forest.agg({'temp': max, 'rain': min})
  
temp    33.3
  rain     0.0
  dtype: float64

확인 결과 최대 온도는 33도이며, 최소 강수량은 0ml 이다.

여러 함수 지정

사전에 리스트를 넘겨주면 하나의 열에만 여러 함수를 적용할 수도 있다.

온도는 최대, 최소 값을 모두 구하고 강수량은 최대값만 구해보자.

forest.agg({'temp':[min, max], 'rain':max})
  
temp rain
max 33.3 6.4
min 2.2 NaN

강수량은 최저값을 구하라고 사전에 지정하지 않았기 때문에 NaN 값이 나온다.

직접 만든 함수 적용

max min

이번에는 내장 함수가 아닌 직접 생성한 함수를 적용시켜보자.

최대값에서 최소값을 빼는 max_min 함수를 적용해보자.

def max_min(x):
      return max(x) - min(x)
  
forest.agg(max_min)
  
X          8.00
  Y          7.00
  temp      31.10
  wind       9.00
  rain       6.40
  area    1090.84
  dtype: float64

각 열마다 최대값에서 최소값을 뺀 결과가 나왔다.

lambda

위의 함수를 람다로 표현하면 다음과 같다.

forest.agg(lambda x: max(x) - min(x))
  
X          8.00
  Y          7.00
  temp      31.10
  wind       9.00
  rain       6.40
  area    1090.84
  dtype: float64
count max

열마다 가장 많이 등장하는 값과 빈도를 구해주는 max_count 함수를 만들어보자.

def max_count(x):
      max_index = x.value_counts().idxmax()  # 가장 자주 등장하는 값
      max_value = x.value_counts().max()  # 등장 빈도
      return max_index, max_value
  
forest.agg(max_count)
  
X        (4.0, 91)
  Y       (4.0, 203)
  temp     (17.4, 8)
  wind     (2.2, 53)
  rain    (0.0, 509)
  area    (0.0, 247)
  dtype: object

X 에서 가장 많이 등장하는 값은 4이며 91번 등장했다는 것을 알 수 있다.

groupby 에서 agg 사용하기

groupby 로 그룹을 만든 후, agg 를 사용할 수 있다.

예시로 X 값을 그룹으로 잡아보자.

grouped = forest.groupby('X')
  

이제 각 그룹당 최고값을 구하도록 .agg(max) 를 구해보자.

grouped.agg(max)
  
Y temp wind rain area
X
1 5 32.4 8.5 0.0 212.88
2 5 33.1 9.4 0.0 200.94
3 6 32.3 8.5 0.0 35.88
4 6 32.6 8.5 0.4 154.88
5 6 27.6 8.0 1.4 24.24
6 6 33.3 9.4 0.0 1090.84
7 6 27.3 9.4 6.4 278.53
8 8 31.0 8.9 0.8 746.28
9 9 30.2 4.5 0.0 105.66

X 좌표가 1 일때, 풍량이 8.5 인데 비해, X 좌표가 9 일때, 풍량이 4.5 인것을 보니, X 가 1 인 좌표는 최고 바람세기가 높은 곳임을 알 수 있다.

groupby 에서 컬럼 직접 지정

앞에서와 마찬가지로 사전으로 각 열마다 다른 함수를 적용할 수 있다.

온도는 최고값을, 강수량은 총 강수량과 최대강수량을 구해보자.

forest.groupby('X').agg({'temp':max, 'rain':[sum, max]})
  
temp rain
max sum max
X
1 32.4 0.0 0.0
2 33.1 0.0 0.0
3 32.3 0.0 0.0
4 32.6 0.4 0.4
5 27.6 1.4 1.4
6 33.3 0.0 0.0
7 27.3 8.4 6.4
8 31.0 1.0 0.8
9 30.2 0.0 0.0

X 좌표마다 최고온도, 강수량의 합과 최고값을 볼 수 있다.

12.2.5. pivot table

엑셀에서 피봇 테이블을 판다스로 구현해보자.

피봇 테이블이란, 열과 행의 그룹들을 값으로 보는 방식을 의미한다.

산불 데이터를 예시로 피봇 테이블에 대해 알아보자.

import pandas as pd
  
forest = pd.read_excel('forestfires.xlsx')
  
forest.head()
  
X Y month day FFMC DMC DC ISI temp RH wind rain area
0 7 5 mar fri 86.2 26.2 94.3 5.1 8.2 51 6.7 0.0 0.0
1 7 4 oct tue 90.6 35.4 669.1 6.7 18.0 33 0.9 0.0 0.0
2 7 4 oct sat 90.6 43.7 686.9 6.7 14.6 33 1.3 0.0 0.0
3 8 6 mar fri 91.7 33.3 77.5 9.0 8.3 97 4.0 0.2 0.0
4 8 6 mar sun 89.3 51.3 102.2 9.6 11.4 99 1.8 0.0 0.0

월별 - 요일별 - 온도 보기

월별 요일별 온도를 피봇 테이블로 나타내보자.

행에는 월을, 열에는 요일을, 그리고 값에는 온도를 나타내자.

forest.pivot_table(index='month', columns='day', values='temp')
  
day fri mon sat sun thu tue wed
month
apr 16.700000 10.900000 9.300000 14.900000 5.800000 NaN 15.200000
aug 21.238095 22.693333 20.503448 21.257500 21.407692 21.842857 23.228000
dec 2.200000 4.600000 NaN 4.800000 5.100000 5.100000 5.100000
feb 11.680000 8.900000 11.725000 8.875000 6.700000 4.850000 8.800000
jan NaN NaN 5.300000 5.200000 NaN NaN NaN
jul 17.033333 21.575000 22.450000 24.360000 29.200000 22.500000 15.366667
jun 20.533333 18.033333 17.550000 18.225000 24.550000 NaN 25.200000
mar 14.000000 11.783333 15.430000 10.628571 11.420000 15.100000 12.450000
may 18.000000 NaN 11.300000 NaN NaN NaN NaN
nov NaN NaN NaN NaN NaN 11.800000 NaN
oct 11.300000 17.175000 16.933333 16.600000 NaN 19.850000 18.050000
sep 18.586842 18.125000 21.524000 20.437037 20.390476 18.721053 20.407143

피봇 테이블의 기본적으로 평균값을 구해준다.

fill value

결측값이 있어 NaN 이 자주 보인다. fill_value=0 이라는 옵션을 넘겨 NaN0 으로 바꿔보자.

forest.pivot_table(index='month', columns='day', values='temp', fill_value=0)
  
day fri mon sat sun thu tue wed
month
apr 16.700000 10.900000 9.300000 14.900000 5.800000 0.000000 15.200000
aug 21.238095 22.693333 20.503448 21.257500 21.407692 21.842857 23.228000
dec 2.200000 4.600000 0.000000 4.800000 5.100000 5.100000 5.100000
feb 11.680000 8.900000 11.725000 8.875000 6.700000 4.850000 8.800000
jan 0.000000 0.000000 5.300000 5.200000 0.000000 0.000000 0.000000
jul 17.033333 21.575000 22.450000 24.360000 29.200000 22.500000 15.366667
jun 20.533333 18.033333 17.550000 18.225000 24.550000 0.000000 25.200000
mar 14.000000 11.783333 15.430000 10.628571 11.420000 15.100000 12.450000
may 18.000000 0.000000 11.300000 0.000000 0.000000 0.000000 0.000000
nov 0.000000 0.000000 0.000000 0.000000 0.000000 11.800000 0.000000
oct 11.300000 17.175000 16.933333 16.600000 0.000000 19.850000 18.050000
sep 18.586842 18.125000 21.524000 20.437037 20.390476 18.721053 20.407143

결측값이 모두 0 으로 처리되었다.

aggfunc

평균값이 아닌, 합을 구하고 싶다면 aggfunc=sum 이라는 옵션을 넘겨줄 수 있다.

forest.pivot_table(index='month', columns='day', values='temp', fill_value=0, aggfunc=sum)
  
day fri mon sat sun thu tue wed
month
apr 16.7 10.9 9.3 44.7 11.6 0.0 15.2
aug 446.0 340.4 594.6 850.3 556.6 611.6 580.7
dec 2.2 18.4 0.0 4.8 5.1 5.1 5.1
feb 58.4 26.7 46.9 35.5 6.7 9.7 8.8
jan 0.0 0.0 5.3 5.2 0.0 0.0 0.0
jul 51.1 86.3 179.6 121.8 87.6 135.0 46.1
jun 61.6 54.1 35.1 72.9 49.1 0.0 75.6
mar 154.0 141.4 154.3 74.4 57.1 75.5 49.8
may 18.0 0.0 11.3 0.0 0.0 0.0 0.0
nov 0.0 0.0 0.0 0.0 0.0 11.8 0.0
oct 11.3 68.7 50.8 49.8 0.0 39.7 36.1
sep 706.3 507.5 538.1 551.8 428.2 355.7 285.7

마찬가지로 최소값을 구하고 싶다면 aggfunc=min 이라는 옵션을 넘겨줄 수 있다.

forest.pivot_table(index='month', columns='day', values='temp', fill_value=0, aggfunc=min)
  
day fri mon sat sun thu tue wed
month
apr 16.7 10.9 9.3 13.4 5.8 0.0 15.2
aug 11.2 8.0 5.1 10.4 16.2 14.4 16.6
dec 2.2 4.6 0.0 4.8 5.1 5.1 5.1
feb 7.5 5.3 4.6 4.2 6.7 4.6 8.8
jan 0.0 0.0 5.3 5.2 0.0 0.0 0.0
jul 13.4 17.9 16.6 18.2 27.2 17.1 12.6
jun 19.2 14.3 10.6 14.3 22.7 0.0 19.6
mar 8.2 8.3 11.6 5.5 5.3 14.1 8.9
may 18.0 0.0 11.3 0.0 0.0 0.0 0.0
nov 0.0 0.0 0.0 0.0 0.0 11.8 0.0
oct 11.3 16.1 14.6 13.8 0.0 18.0 15.9
sep 10.1 9.8 15.4 12.2 12.9 13.1 10.5

월별 - 요일별 - X 좌표별 - 온도 보기

만들어 놓은 피봇 테이블에 X 좌표도 추가해서 표현할 수 있다. 행에 'month'['month', 'day'] 로 바꾸고 columns='X' 라고 바꿔보자.

forest.pivot_table(index=['month', 'day'], columns='X', values='temp', fill_value=0)
  
X 1 2 3 4 5 6 7 8 9
month day
apr fri 0.000000 0.000000 0.000000 16.700000 0.00 0.000000 0.000000 0.000000 0.0
mon 0.000000 0.000000 0.000000 0.000000 0.00 10.900000 0.000000 0.000000 0.0
sat 0.000000 0.000000 0.000000 0.000000 0.00 9.300000 0.000000 0.000000 0.0
sun 0.000000 0.000000 0.000000 0.000000 17.60 13.700000 13.400000 0.000000 0.0
thu 0.000000 0.000000 0.000000 0.000000 0.00 5.800000 0.000000 0.000000 0.0
wed 0.000000 0.000000 0.000000 0.000000 0.00 15.200000 0.000000 0.000000 0.0
aug fri 21.857143 23.600000 11.200000 21.800000 21.10 19.450000 23.300000 21.000000 25.0
mon 0.000000 27.475000 13.700000 32.600000 0.00 23.900000 17.700000 21.483333 0.0
sat 21.860000 21.825000 19.100000 19.900000 0.00 22.750000 18.983333 20.075000 0.0
sun 18.700000 22.430000 21.000000 20.275000 22.85 19.100000 19.200000 22.257143 0.0
thu 24.366667 20.150000 18.200000 22.466667 20.30 22.200000 21.400000 21.666667 20.5
tue 20.100000 22.537500 22.566667 19.580000 22.60 26.350000 24.450000 18.700000 0.0
wed 19.933333 23.900000 21.700000 23.733333 0.00 20.700000 0.000000 25.216667 0.0
dec fri 0.000000 0.000000 0.000000 2.200000 0.00 0.000000 0.000000 0.000000 0.0
mon 0.000000 0.000000 4.600000 4.600000 0.00 0.000000 0.000000 0.000000 0.0
sun 0.000000 0.000000 0.000000 4.800000 0.00 0.000000 0.000000 0.000000 0.0
thu 0.000000 0.000000 0.000000 5.100000 0.00 0.000000 0.000000 0.000000 0.0
tue 0.000000 0.000000 0.000000 0.000000 0.00 5.100000 0.000000 0.000000 0.0
wed 0.000000 0.000000 0.000000 0.000000 0.00 0.000000 0.000000 5.100000 0.0
feb fri 0.000000 12.300000 0.000000 0.000000 7.50 14.700000 8.200000 0.000000 15.7
mon 0.000000 13.900000 0.000000 0.000000 0.00 5.300000 7.500000 0.000000 0.0
sat 0.000000 4.600000 12.700000 14.800000 0.00 0.000000 0.000000 0.000000 0.0
sun 0.000000 0.000000 0.000000 10.100000 12.40 4.200000 8.800000 0.000000 0.0
thu 0.000000 0.000000 0.000000 0.000000 0.00 0.000000 0.000000 0.000000 6.7
tue 0.000000 0.000000 0.000000 0.000000 0.00 4.850000 0.000000 0.000000 0.0
wed 0.000000 0.000000 8.800000 0.000000 0.00 0.000000 0.000000 0.000000 0.0
jan sat 0.000000 5.300000 0.000000 0.000000 0.00 0.000000 0.000000 0.000000 0.0
sun 0.000000 0.000000 0.000000 5.200000 0.00 0.000000 0.000000 0.000000 0.0
jul fri 14.800000 13.400000 0.000000 22.900000 0.00 0.000000 0.000000 0.000000 0.0
mon 0.000000 0.000000 17.900000 0.000000 0.00 23.000000 22.600000 0.000000 22.8
... ... ... ... ... ... ... ... ... ...
wed 0.000000 0.000000 14.200000 0.000000 19.30 0.000000 12.600000 0.000000 0.0
jun fri 0.000000 0.000000 19.200000 0.000000 0.00 23.200000 0.000000 0.000000 0.0
mon 0.000000 0.000000 0.000000 0.000000 0.00 19.900000 0.000000 14.300000 0.0
sat 0.000000 0.000000 0.000000 0.000000 0.00 10.600000 0.000000 0.000000 24.5
sun 0.000000 0.000000 0.000000 0.000000 0.00 14.300000 21.600000 15.400000 0.0
thu 0.000000 22.700000 0.000000 26.400000 0.00 0.000000 0.000000 0.000000 0.0
wed 0.000000 0.000000 0.000000 0.000000 0.00 0.000000 0.000000 19.600000 28.0
mar fri 0.000000 0.000000 18.800000 14.850000 15.60 13.150000 8.200000 12.850000 0.0
mon 8.300000 0.000000 9.800000 11.950000 13.20 12.700000 13.550000 0.000000 0.0
sat 0.000000 0.000000 14.733333 17.000000 15.10 15.300000 0.000000 0.000000 0.0
sun 0.000000 8.500000 0.000000 10.600000 0.00 12.400000 11.500000 11.450000 0.0
thu 0.000000 0.000000 0.000000 18.200000 11.60 9.100000 0.000000 0.000000 0.0
tue 0.000000 15.200000 15.400000 14.100000 0.00 0.000000 0.000000 0.000000 0.0
wed 0.000000 0.000000 10.050000 0.000000 0.00 14.850000 0.000000 0.000000 0.0
may fri 0.000000 0.000000 0.000000 18.000000 0.00 0.000000 0.000000 0.000000 0.0
sat 0.000000 0.000000 0.000000 0.000000 0.00 11.300000 0.000000 0.000000 0.0
nov tue 0.000000 0.000000 0.000000 0.000000 0.00 11.800000 0.000000 0.000000 0.0
oct fri 0.000000 0.000000 0.000000 0.000000 0.00 0.000000 11.300000 0.000000 0.0
mon 0.000000 0.000000 0.000000 0.000000 0.00 0.000000 16.450000 17.900000 0.0
sat 0.000000 0.000000 0.000000 18.400000 0.00 0.000000 16.200000 0.000000 0.0
sun 0.000000 15.400000 20.600000 13.800000 0.00 0.000000 0.000000 0.000000 0.0
tue 0.000000 0.000000 0.000000 0.000000 0.00 21.700000 18.000000 0.000000 0.0
wed 0.000000 0.000000 15.900000 0.000000 0.00 0.000000 0.000000 20.200000 0.0
sep fri 20.100000 19.600000 18.250000 18.650000 16.15 19.100000 18.914286 20.700000 0.0
mon 18.200000 19.833333 17.250000 14.966667 16.90 18.250000 19.350000 18.100000 0.0
sat 25.366667 23.633333 24.200000 19.640000 24.10 20.771429 20.350000 17.800000 0.0
sun 22.066667 17.700000 21.380000 20.016667 0.00 21.766667 18.733333 17.800000 0.0
thu 21.525000 0.000000 17.850000 21.071429 21.60 17.750000 19.400000 23.450000 0.0
tue 20.700000 17.600000 15.900000 16.500000 0.00 19.180000 20.700000 18.650000 24.3
wed 0.000000 20.050000 18.500000 20.540000 23.90 25.350000 12.950000 0.000000 0.0

64 rows × 9 columns

행이 멀티 인덱스로 나뉘어진것을 볼 수 있다.

열을 멀티 컬럼으로 나누고 싶다면, 리스트를 열에 넣을 수 있다.

한눈에 보기 편하게 index='X'라고 두고, columns=['month', 'day'] 라고 표현하자.

forest.pivot_table(index='X', columns=['month', 'day'], values='temp', fill_value=0)
  
month apr aug ... oct sep
day fri mon sat sun thu wed fri mon sat sun ... sun tue wed fri mon sat sun thu tue wed
X
1 0.0 0.0 0.0 0.0 0.0 0.0 21.857143 0.000000 21.860000 18.700000 ... 0.0 0.0 0.0 20.100000 18.200000 25.366667 22.066667 21.525000 20.70 0.00
2 0.0 0.0 0.0 0.0 0.0 0.0 23.600000 27.475000 21.825000 22.430000 ... 15.4 0.0 0.0 19.600000 19.833333 23.633333 17.700000 0.000000 17.60 20.05
3 0.0 0.0 0.0 0.0 0.0 0.0 11.200000 13.700000 19.100000 21.000000 ... 20.6 0.0 15.9 18.250000 17.250000 24.200000 21.380000 17.850000 15.90 18.50
4 16.7 0.0 0.0 0.0 0.0 0.0 21.800000 32.600000 19.900000 20.275000 ... 13.8 0.0 0.0 18.650000 14.966667 19.640000 20.016667 21.071429 16.50 20.54
5 0.0 0.0 0.0 17.6 0.0 0.0 21.100000 0.000000 0.000000 22.850000 ... 0.0 0.0 0.0 16.150000 16.900000 24.100000 0.000000 21.600000 0.00 23.90
6 0.0 10.9 9.3 13.7 5.8 15.2 19.450000 23.900000 22.750000 19.100000 ... 0.0 21.7 0.0 19.100000 18.250000 20.771429 21.766667 17.750000 19.18 25.35
7 0.0 0.0 0.0 13.4 0.0 0.0 23.300000 17.700000 18.983333 19.200000 ... 0.0 18.0 0.0 18.914286 19.350000 20.350000 18.733333 19.400000 20.70 12.95
8 0.0 0.0 0.0 0.0 0.0 0.0 21.000000 21.483333 20.075000 22.257143 ... 0.0 0.0 20.2 20.700000 18.100000 17.800000 17.800000 23.450000 18.65 0.00
9 0.0 0.0 0.0 0.0 0.0 0.0 25.000000 0.000000 0.000000 0.000000 ... 0.0 0.0 0.0 0.000000 0.000000 0.000000 0.000000 0.000000 24.30 0.00

9 rows × 64 columns

12.2.6. 다중 인덱스

여러 그룹을 데이터 프레임으로 나타내는 것을 다중 인덱스(multi index) 라고 부른다.

예시로 고객의 신상 정보와, 정기예금 가입여부를 모아놓은 데이터를 사용하자

import pandas as pd
  
bank = pd.read_csv('bank.csv', sep=';')
  
bank.head()
  
age job marital education default balance housing loan contact day month duration campaign pdays previous poutcome y
0 30 unemployed married primary no 1787 no no cellular 19 oct 79 1 -1 0 unknown no
1 33 services married secondary no 4789 yes yes cellular 11 may 220 1 339 4 failure no
2 35 management single tertiary no 1350 yes no cellular 16 apr 185 1 330 1 failure no
3 30 management married tertiary no 1476 yes yes unknown 3 jun 199 4 -1 0 unknown no
4 59 blue-collar married secondary no 0 yes no unknown 5 may 226 1 -1 0 unknown no

pivot table

대표적으로, 피봇 테이블을 만들때 다중 인덱스가 만들어진다.

정기예금 가입 여부 별 결혼유무별 직업의 통장 잔고값을 피봇 테이블로 만들어보자.

pivot_df = bank.pivot_table(index=['y', 'marital'], columns='job', values='balance', fill_value=0)
  
pivot_df
  
job admin. blue-collar entrepreneur housemaid management retired self-employed services student technician unemployed unknown
y marital
no divorced 854.431034 788.956522 1102.538462 405.444444 1313.990654 1622.814815 2921.600000 974.666667 0.000000 879.012346 842.909091 137.000000
married 1357.608511 1038.893617 1636.918699 1758.610390 1889.041667 2289.092857 1379.554622 1242.354545 519.222222 1437.311653 1048.075758 1201.208333
single 1106.692913 1451.366667 2738.823529 3309.916667 1743.912351 3909.666667 896.882353 877.323810 1825.732143 1286.365957 1272.592593 3108.333333
yes divorced 1628.363636 440.100000 -32.333333 2878.000000 2487.750000 2022.312500 1873.000000 640.625000 0.000000 509.375000 0.000000 0.000000
married 1320.354839 1543.142857 1283.555556 1768.857143 1665.090909 2752.388889 925.375000 1031.000000 10.000000 1433.476190 1744.000000 1574.833333
single 1149.500000 517.375000 897.333333 10237.000000 1643.571429 1247.500000 2023.714286 1475.714286 1264.277778 1638.181818 418.250000 0.000000

행이 두 단계로 만들어진것을 볼 수 있다. 이를 다중 인덱스라고 부른다.

melt

다중 인덱스 데이터 프레임에서 자료를 사용할때 데이터 프레임이 일렬인 상태로 놓일때 더욱 접근하기 쉽다.

.melt() 를 사용해 다중 인덱스를 풀어보자.

melted_df = pd.melt(pivot_df)
  

첫 12 자료를 살펴보자.

melted_df.iloc[:12]
  
job value
0 admin. 854.431034
1 admin. 1357.608511
2 admin. 1106.692913
3 admin. 1628.363636
4 admin. 1320.354839
5 admin. 1149.500000
6 blue-collar 788.956522
7 blue-collar 1038.893617
8 blue-collar 1451.366667
9 blue-collar 440.100000
10 blue-collar 1543.142857
11 blue-collar 517.375000

pivot_df 의 모든 열을 풀어, 하나의 긴 데이터 프레임으로 만들어졌다.

12.3. merging, joining, concatenating

12.3.1. append

존재하는 데이터 프레임에 자료를 추가하는 것을 어팬드(append) 라고 부른다.

예제 데이터로 산불 자료를 사용하자.

import pandas as pd
  
df = pd.read_excel('forestfires.xlsx')
  
df.head()
  
X Y month day FFMC DMC DC ISI temp RH wind rain area
0 7 5 mar fri 86.2 26.2 94.3 5.1 8.2 51 6.7 0.0 0.0
1 7 4 oct tue 90.6 35.4 669.1 6.7 18.0 33 0.9 0.0 0.0
2 7 4 oct sat 90.6 43.7 686.9 6.7 14.6 33 1.3 0.0 0.0
3 8 6 mar fri 91.7 33.3 77.5 9.0 8.3 97 4.0 0.2 0.0
4 8 6 mar sun 89.3 51.3 102.2 9.6 11.4 99 1.8 0.0 0.0

데이터 준비

연습을 위해 임의로 두 데이터 프레임 df1df2 를 만들자.

df1 = df.iloc[:10]  # 첫 열줄
  
df1.head()
  
X Y month day FFMC DMC DC ISI temp RH wind rain area
0 7 5 mar fri 86.2 26.2 94.3 5.1 8.2 51 6.7 0.0 0.0
1 7 4 oct tue 90.6 35.4 669.1 6.7 18.0 33 0.9 0.0 0.0
2 7 4 oct sat 90.6 43.7 686.9 6.7 14.6 33 1.3 0.0 0.0
3 8 6 mar fri 91.7 33.3 77.5 9.0 8.3 97 4.0 0.2 0.0
4 8 6 mar sun 89.3 51.3 102.2 9.6 11.4 99 1.8 0.0 0.0
df2 = df.iloc[-10:]  # 마지막 열줄
  
df2.head()
  
X Y month day FFMC DMC DC ISI temp RH wind rain area
507 2 4 aug fri 91.0 166.9 752.6 7.1 25.9 41 3.6 0.0 0.00
508 1 2 aug fri 91.0 166.9 752.6 7.1 25.9 41 3.6 0.0 0.00
509 5 4 aug fri 91.0 166.9 752.6 7.1 21.1 71 7.6 1.4 2.17
510 6 5 aug fri 91.0 166.9 752.6 7.1 18.2 62 5.4 0.0 0.43
511 8 6 aug sun 81.6 56.7 665.6 1.9 27.8 35 2.7 0.0 0.00

기본 어팬드

df1 뒤에 df2 를 붙여보자.

df1.append(df2)
  
X Y month day FFMC DMC DC ISI temp RH wind rain area
0 7 5 mar fri 86.2 26.2 94.3 5.1 8.2 51 6.7 0.0 0.00
1 7 4 oct tue 90.6 35.4 669.1 6.7 18.0 33 0.9 0.0 0.00
2 7 4 oct sat 90.6 43.7 686.9 6.7 14.6 33 1.3 0.0 0.00
3 8 6 mar fri 91.7 33.3 77.5 9.0 8.3 97 4.0 0.2 0.00
4 8 6 mar sun 89.3 51.3 102.2 9.6 11.4 99 1.8 0.0 0.00
5 8 6 aug sun 92.3 85.3 488.0 14.7 22.2 29 5.4 0.0 0.00
6 8 6 aug mon 92.3 88.9 495.6 8.5 24.1 27 3.1 0.0 0.00
7 8 6 aug mon 91.5 145.4 608.2 10.7 8.0 86 2.2 0.0 0.00
8 8 6 sep tue 91.0 129.5 692.6 7.0 13.1 63 5.4 0.0 0.00
9 7 5 sep sat 92.5 88.0 698.6 7.1 22.8 40 4.0 0.0 0.00
507 2 4 aug fri 91.0 166.9 752.6 7.1 25.9 41 3.6 0.0 0.00
508 1 2 aug fri 91.0 166.9 752.6 7.1 25.9 41 3.6 0.0 0.00
509 5 4 aug fri 91.0 166.9 752.6 7.1 21.1 71 7.6 1.4 2.17
510 6 5 aug fri 91.0 166.9 752.6 7.1 18.2 62 5.4 0.0 0.43
511 8 6 aug sun 81.6 56.7 665.6 1.9 27.8 35 2.7 0.0 0.00
512 4 3 aug sun 81.6 56.7 665.6 1.9 27.8 32 2.7 0.0 6.44
513 2 4 aug sun 81.6 56.7 665.6 1.9 21.9 71 5.8 0.0 54.29
514 7 4 aug sun 81.6 56.7 665.6 1.9 21.2 70 6.7 0.0 11.16
515 1 4 aug sat 94.4 146.0 614.7 11.3 25.6 42 4.0 0.0 0.00
516 6 3 nov tue 79.5 3.0 106.7 1.1 11.8 31 4.5 0.0 0.00

두 데이터 프레임이 합쳐졌다.

ignore index

행 번호를 살펴보면, 9 다음의 숫자가 507 이다. 기존 인덱스를 무시하도록 ignore_index=True 라는 옵션을 넘겨보자.

df1.append(df2, ignore_index=True)
  
X Y month day FFMC DMC DC ISI temp RH wind rain area
0 7 5 mar fri 86.2 26.2 94.3 5.1 8.2 51 6.7 0.0 0.00
1 7 4 oct tue 90.6 35.4 669.1 6.7 18.0 33 0.9 0.0 0.00
2 7 4 oct sat 90.6 43.7 686.9 6.7 14.6 33 1.3 0.0 0.00
3 8 6 mar fri 91.7 33.3 77.5 9.0 8.3 97 4.0 0.2 0.00
4 8 6 mar sun 89.3 51.3 102.2 9.6 11.4 99 1.8 0.0 0.00
5 8 6 aug sun 92.3 85.3 488.0 14.7 22.2 29 5.4 0.0 0.00
6 8 6 aug mon 92.3 88.9 495.6 8.5 24.1 27 3.1 0.0 0.00
7 8 6 aug mon 91.5 145.4 608.2 10.7 8.0 86 2.2 0.0 0.00
8 8 6 sep tue 91.0 129.5 692.6 7.0 13.1 63 5.4 0.0 0.00
9 7 5 sep sat 92.5 88.0 698.6 7.1 22.8 40 4.0 0.0 0.00
10 2 4 aug fri 91.0 166.9 752.6 7.1 25.9 41 3.6 0.0 0.00
11 1 2 aug fri 91.0 166.9 752.6 7.1 25.9 41 3.6 0.0 0.00
12 5 4 aug fri 91.0 166.9 752.6 7.1 21.1 71 7.6 1.4 2.17
13 6 5 aug fri 91.0 166.9 752.6 7.1 18.2 62 5.4 0.0 0.43
14 8 6 aug sun 81.6 56.7 665.6 1.9 27.8 35 2.7 0.0 0.00
15 4 3 aug sun 81.6 56.7 665.6 1.9 27.8 32 2.7 0.0 6.44
16 2 4 aug sun 81.6 56.7 665.6 1.9 21.9 71 5.8 0.0 54.29
17 7 4 aug sun 81.6 56.7 665.6 1.9 21.2 70 6.7 0.0 11.16
18 1 4 aug sat 94.4 146.0 614.7 11.3 25.6 42 4.0 0.0 0.00
19 6 3 nov tue 79.5 3.0 106.7 1.1 11.8 31 4.5 0.0 0.00

이번에는 행번호가 0에서 19까지 메겨졌다.

다중 어팬드

리스트를 사용해 한번에 여러 데이터 프레임을 어팬드할 수 있다.

df3 = df.iloc[10:20]  # 10에서 20 번째 줄
  
df1.append([df2, df3])
  
X Y month day FFMC DMC DC ISI temp RH wind rain area
0 7 5 mar fri 86.2 26.2 94.3 5.1 8.2 51 6.7 0.0 0.00
1 7 4 oct tue 90.6 35.4 669.1 6.7 18.0 33 0.9 0.0 0.00
2 7 4 oct sat 90.6 43.7 686.9 6.7 14.6 33 1.3 0.0 0.00
3 8 6 mar fri 91.7 33.3 77.5 9.0 8.3 97 4.0 0.2 0.00
4 8 6 mar sun 89.3 51.3 102.2 9.6 11.4 99 1.8 0.0 0.00
5 8 6 aug sun 92.3 85.3 488.0 14.7 22.2 29 5.4 0.0 0.00
6 8 6 aug mon 92.3 88.9 495.6 8.5 24.1 27 3.1 0.0 0.00
7 8 6 aug mon 91.5 145.4 608.2 10.7 8.0 86 2.2 0.0 0.00
8 8 6 sep tue 91.0 129.5 692.6 7.0 13.1 63 5.4 0.0 0.00
9 7 5 sep sat 92.5 88.0 698.6 7.1 22.8 40 4.0 0.0 0.00
507 2 4 aug fri 91.0 166.9 752.6 7.1 25.9 41 3.6 0.0 0.00
508 1 2 aug fri 91.0 166.9 752.6 7.1 25.9 41 3.6 0.0 0.00
509 5 4 aug fri 91.0 166.9 752.6 7.1 21.1 71 7.6 1.4 2.17
510 6 5 aug fri 91.0 166.9 752.6 7.1 18.2 62 5.4 0.0 0.43
511 8 6 aug sun 81.6 56.7 665.6 1.9 27.8 35 2.7 0.0 0.00
512 4 3 aug sun 81.6 56.7 665.6 1.9 27.8 32 2.7 0.0 6.44
513 2 4 aug sun 81.6 56.7 665.6 1.9 21.9 71 5.8 0.0 54.29
514 7 4 aug sun 81.6 56.7 665.6 1.9 21.2 70 6.7 0.0 11.16
515 1 4 aug sat 94.4 146.0 614.7 11.3 25.6 42 4.0 0.0 0.00
516 6 3 nov tue 79.5 3.0 106.7 1.1 11.8 31 4.5 0.0 0.00
10 7 5 sep sat 92.5 88.0 698.6 7.1 17.8 51 7.2 0.0 0.00
11 7 5 sep sat 92.8 73.2 713.0 22.6 19.3 38 4.0 0.0 0.00
12 6 5 aug fri 63.5 70.8 665.3 0.8 17.0 72 6.7 0.0 0.00
13 6 5 sep mon 90.9 126.5 686.5 7.0 21.3 42 2.2 0.0 0.00
14 6 5 sep wed 92.9 133.3 699.6 9.2 26.4 21 4.5 0.0 0.00
15 6 5 sep fri 93.3 141.2 713.9 13.9 22.9 44 5.4 0.0 0.00
16 5 5 mar sat 91.7 35.8 80.8 7.8 15.1 27 5.4 0.0 0.00
17 8 5 oct mon 84.9 32.8 664.2 3.0 16.7 47 4.9 0.0 0.00
18 6 4 mar wed 89.2 27.9 70.8 6.3 15.9 35 4.0 0.0 0.00
19 6 4 apr sat 86.3 27.4 97.1 5.1 9.3 44 4.5 0.0 0.00

dataframe + series

이번에는 df1의 끝에 한 줄만 더 추가해보자.

one_row = df.iloc[-1]    # 마지막 줄
  

one_row 는 시리즈임을 알 수 있다.

one_row
  
X            6
  Y            3
  month      nov
  day        tue
  FFMC      79.5
  DMC          3
  DC       106.7
  ISI        1.1
  temp      11.8
  RH          31
  wind       4.5
  rain         0
  area         0
  Name: 516, dtype: object

데이터 프레임에 시리즈를 어팬드해보자.

df1.append(one_row)
  
X Y month day FFMC DMC DC ISI temp RH wind rain area
0 7 5 mar fri 86.2 26.2 94.3 5.1 8.2 51 6.7 0.0 0.0
1 7 4 oct tue 90.6 35.4 669.1 6.7 18.0 33 0.9 0.0 0.0
2 7 4 oct sat 90.6 43.7 686.9 6.7 14.6 33 1.3 0.0 0.0
3 8 6 mar fri 91.7 33.3 77.5 9.0 8.3 97 4.0 0.2 0.0
4 8 6 mar sun 89.3 51.3 102.2 9.6 11.4 99 1.8 0.0 0.0
5 8 6 aug sun 92.3 85.3 488.0 14.7 22.2 29 5.4 0.0 0.0
6 8 6 aug mon 92.3 88.9 495.6 8.5 24.1 27 3.1 0.0 0.0
7 8 6 aug mon 91.5 145.4 608.2 10.7 8.0 86 2.2 0.0 0.0
8 8 6 sep tue 91.0 129.5 692.6 7.0 13.1 63 5.4 0.0 0.0
9 7 5 sep sat 92.5 88.0 698.6 7.1 22.8 40 4.0 0.0 0.0
516 6 3 nov tue 79.5 3.0 106.7 1.1 11.8 31 4.5 0.0 0.0

자료 유형이 달라도 가능하다.

12.3.2. concatenate

concatenate 방식으로 두 개 이상의 데이터 프레임을 합쳐보자.

산불 데이터를 예제로 사용하자.

import pandas as pd
  
df = pd.read_excel('forestfires.xlsx')
  
df.head()
  
X Y month day FFMC DMC DC ISI temp RH wind rain area
0 7 5 mar fri 86.2 26.2 94.3 5.1 8.2 51 6.7 0.0 0.0
1 7 4 oct tue 90.6 35.4 669.1 6.7 18.0 33 0.9 0.0 0.0
2 7 4 oct sat 90.6 43.7 686.9 6.7 14.6 33 1.3 0.0 0.0
3 8 6 mar fri 91.7 33.3 77.5 9.0 8.3 97 4.0 0.2 0.0
4 8 6 mar sun 89.3 51.3 102.2 9.6 11.4 99 1.8 0.0 0.0

임의로 데이터 프레임을 나눠보자.

xy = df[['X', 'Y']][:5]    # X, Y 열의 첫 5 줄
  
xy
  
X Y
0 7 5
1 7 4
2 7 4
3 8 6
4 8 6
xy2 = df[['X', 'Y']][-5:]    # X, Y 열의 마지막 5 줄
  
xy2
  
X Y
512 4 3
513 2 4
514 7 4
515 1 4
516 6 3

axis=0

pd.concat()을 사용해 수직으로 xyxy2를 연결시켜보자.

pd.concat([xy, xy2])
  
X Y
0 7 5
1 7 4
2 7 4
3 8 6
4 8 6
512 4 3
513 2 4
514 7 4
515 1 4
516 6 3

append 처럼 두 데이터 프레임이 합쳐졌다.

ignore index

행이름을 무시하도록 ignore_index=True옵션을 넘겨보자.

pd.concat([xy, xy2], ignore_index=True)
  
X Y
0 7 5
1 7 4
2 7 4
3 8 6
4 8 6
5 4 3
6 2 4
7 7 4
8 1 4
9 6 3

행 이름이 다시 정리되었다.

axis=1

append 에서는 수평으로 데이터를 합칠 수 없지만, concat 에서는 axis=1 옵션으로 가능하다.

예시를 보이기 위해 wind 의 첫 5줄을 wind라고 저장하자.

wind = df[['wind']][:5]  # 첫 5 줄
  
wind
  
wind
0 6.7
1 0.9
2 1.3
3 4.0
4 1.8

xywind를 수평으로 합쳐보자.

pd.concat([xy, wind])
  
X Y wind
0 7.0 5.0 NaN
1 7.0 4.0 NaN
2 7.0 4.0 NaN
3 8.0 6.0 NaN
4 8.0 6.0 NaN
0 NaN NaN 6.7
1 NaN NaN 0.9
2 NaN NaN 1.3
3 NaN NaN 4.0
4 NaN NaN 1.8

wind 라는 열이 xy에 존재하지 않기 때문에 판다스는 자동으로 wind 라는 열을 수평으로 붙였다.

하지만 wind 의 열의 값이 NaN으로 표현되고, 수직으로 자료가 붙었다. 이는 concat 의 초기값에 axis=0이 기본으로 들어있기 때문이다.

axis=1옵션을 넘겨보자.

pd.concat([xy, wind], axis=1)
  
X Y wind
0 7 5 6.7
1 7 4 0.9
2 7 4 1.3
3 8 6 4.0
4 8 6 1.8

원하는대로 자료가 수평으로 합쳐졌다.

join 옵션

concat 메써드는 기본적으로 두 데이터 프레임의 합집합을 보여준다. 이는 초기값 중에 join='outer가 기본값으로 들어있기 때문이다. 만약 두 데이터 프레임의 교집합을 보고 싶다면, join='inner' 로 해결할 수 있다.

X 열이 두 데이터 프레임에서 겹치도록 wind2 를 만들어보자.

wind2 = df[['wind', 'X']][:5]
  
wind2
  
wind X
0 6.7 7
1 0.9 7
2 1.3 7
3 4.0 8
4 1.8 8

xy의 모양은 다음과 같다.

xy
  
X Y
0 7 5
1 7 4
2 7 4
3 8 6
4 8 6

이 둘을 concat 으로 합쳐보자.

pd.concat([xy, wind2])
  
X Y wind
0 7 5.0 NaN
1 7 4.0 NaN
2 7 4.0 NaN
3 8 6.0 NaN
4 8 6.0 NaN
0 7 NaN 6.7
1 7 NaN 0.9
2 7 NaN 1.3
3 8 NaN 4.0
4 8 NaN 1.8

확인결과, wind 라는 열이 수평으로 생기기는 했지만, 수직으로 데이터 프레임이 합쳐졌다.

이번에는 join='inner'옵션을 넘겨보자.

'inner' 옵션은 데이터 프레임의 교집합만 나타낼것을 의미한다.

pd.concat([xy, wind2], join='inner')
  
X
0 7
1 7
2 7
3 8
4 8
0 7
1 7
2 7
3 8
4 8

Y와 wind 는 새로운 데이터 프레임에서 빠졌다.

12.3.3. replace

데이터 프레임 내에 있는 특정 값을, 원하는 값으로 변경할 수 있다.

값을 변경할 때는 판다스의 .replace() 메써드를 사용한다.

예시로 adult 데이터를 살펴보자.

import pandas as pd
  
df = pd.read_csv('adult.csv', header=None, skipinitialspace=True)
  
df.head()
  
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
0 39 State-gov 77516 Bachelors 13 Never-married Adm-clerical Not-in-family White Male 2174 0 40 United-States <=50K
1 50 Self-emp-not-inc 83311 Bachelors 13 Married-civ-spouse Exec-managerial Husband White Male 0 0 13 United-States <=50K
2 38 Private 215646 HS-grad 9 Divorced Handlers-cleaners Not-in-family White Male 0 0 40 United-States <=50K
3 53 Private 234721 11th 7 Married-civ-spouse Handlers-cleaners Husband Black Male 0 0 40 United-States <=50K
4 28 Private 338409 Bachelors 13 Married-civ-spouse Prof-specialty Wife Black Female 0 0 40 Cuba <=50K

성별 바꾸기

9번 열의 Male, Female 값을 '남', '여' 값으로 바꿔보자. .replace() 안에, 찾아야할 값과, 바꿀 값을 넘겨주면 된다.

df.replace('Male', '남').head()
  
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
0 39 State-gov 77516 Bachelors 13 Never-married Adm-clerical Not-in-family White 2174 0 40 United-States <=50K
1 50 Self-emp-not-inc 83311 Bachelors 13 Married-civ-spouse Exec-managerial Husband White 0 0 13 United-States <=50K
2 38 Private 215646 HS-grad 9 Divorced Handlers-cleaners Not-in-family White 0 0 40 United-States <=50K
3 53 Private 234721 11th 7 Married-civ-spouse Handlers-cleaners Husband Black 0 0 40 United-States <=50K
4 28 Private 338409 Bachelors 13 Married-civ-spouse Prof-specialty Wife Black Female 0 0 40 Cuba <=50K

9번 열의 값이 바뀌었다. 이번에는 Female 을 '여'로 값을 바꿔보자.

df.replace('Female', '여').head()
  
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
0 39 State-gov 77516 Bachelors 13 Never-married Adm-clerical Not-in-family White Male 2174 0 40 United-States <=50K
1 50 Self-emp-not-inc 83311 Bachelors 13 Married-civ-spouse Exec-managerial Husband White Male 0 0 13 United-States <=50K
2 38 Private 215646 HS-grad 9 Divorced Handlers-cleaners Not-in-family White Male 0 0 40 United-States <=50K
3 53 Private 234721 11th 7 Married-civ-spouse Handlers-cleaners Husband Black Male 0 0 40 United-States <=50K
4 28 Private 338409 Bachelors 13 Married-civ-spouse Prof-specialty Wife Black 0 0 40 Cuba <=50K

만약 이를 df 에 저장하고 싶다면 inplace=True옵션을 넘겨야 한다.

df.replace('Female', '여', inplace=True)
  
df.head()
  
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
0 39 State-gov 77516 Bachelors 13 Never-married Adm-clerical Not-in-family White Male 2174 0 40 United-States <=50K
1 50 Self-emp-not-inc 83311 Bachelors 13 Married-civ-spouse Exec-managerial Husband White Male 0 0 13 United-States <=50K
2 38 Private 215646 HS-grad 9 Divorced Handlers-cleaners Not-in-family White Male 0 0 40 United-States <=50K
3 53 Private 234721 11th 7 Married-civ-spouse Handlers-cleaners Husband Black Male 0 0 40 United-States <=50K
4 28 Private 338409 Bachelors 13 Married-civ-spouse Prof-specialty Wife Black 0 0 40 Cuba <=50K
한번에 변경하기
사전

사전을 이용해 한번에 이를 바꿀 수 있다.

df.replace({'Male': '남', 'Female': '여'}).head()
  
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
0 39 State-gov 77516 Bachelors 13 Never-married Adm-clerical Not-in-family White 2174 0 40 United-States <=50K
1 50 Self-emp-not-inc 83311 Bachelors 13 Married-civ-spouse Exec-managerial Husband White 0 0 13 United-States <=50K
2 38 Private 215646 HS-grad 9 Divorced Handlers-cleaners Not-in-family White 0 0 40 United-States <=50K
3 53 Private 234721 11th 7 Married-civ-spouse Handlers-cleaners Husband Black 0 0 40 United-States <=50K
4 28 Private 338409 Bachelors 13 Married-civ-spouse Prof-specialty Wife Black 0 0 40 Cuba <=50K
리스트

리스트를 사용할수도 있다.

df.replace(['Male', 'Female'], ['남', '여']).head()
  
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
0 39 State-gov 77516 Bachelors 13 Never-married Adm-clerical Not-in-family White 2174 0 40 United-States <=50K
1 50 Self-emp-not-inc 83311 Bachelors 13 Married-civ-spouse Exec-managerial Husband White 0 0 13 United-States <=50K
2 38 Private 215646 HS-grad 9 Divorced Handlers-cleaners Not-in-family White 0 0 40 United-States <=50K
3 53 Private 234721 11th 7 Married-civ-spouse Handlers-cleaners Husband Black 0 0 40 United-States <=50K
4 28 Private 338409 Bachelors 13 Married-civ-spouse Prof-specialty Wife Black 0 0 40 Cuba <=50K

regex

regex=True 옵션을 넘기면 정규표현을 사용해 문자열을 바꿀수도 있다. 문자열에서 간혹 보이는 '-' 표시를 ' ' 표시로 바꿔보자.

df.head()
  
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
0 39 State-gov 77516 Bachelors 13 Never-married Adm-clerical Not-in-family White Male 2174 0 40 United-States <=50K
1 50 Self-emp-not-inc 83311 Bachelors 13 Married-civ-spouse Exec-managerial Husband White Male 0 0 13 United-States <=50K
2 38 Private 215646 HS-grad 9 Divorced Handlers-cleaners Not-in-family White Male 0 0 40 United-States <=50K
3 53 Private 234721 11th 7 Married-civ-spouse Handlers-cleaners Husband Black Male 0 0 40 United-States <=50K
4 28 Private 338409 Bachelors 13 Married-civ-spouse Prof-specialty Wife Black 0 0 40 Cuba <=50K
df.replace(r'-', ' ', regex=True).head()
  
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
0 39 State gov 77516 Bachelors 13 Never married Adm clerical Not in family White Male 2174 0 40 United States <=50K
1 50 Self emp not inc 83311 Bachelors 13 Married civ spouse Exec managerial Husband White Male 0 0 13 United States <=50K
2 38 Private 215646 HS grad 9 Divorced Handlers cleaners Not in family White Male 0 0 40 United States <=50K
3 53 Private 234721 11th 7 Married civ spouse Handlers cleaners Husband Black Male 0 0 40 United States <=50K
4 28 Private 338409 Bachelors 13 Married civ spouse Prof specialty Wife Black 0 0 40 Cuba <=50K

하이픈이 모두 공백으로 바뀌었다.

13. 기계학습

13.1. 데이터셋

UCI 기계학습 저장소에서 와인 퀄리티 데이터셋를 사용한다.

13.2. 데이터 불러오기

Python 코드를 이용해 다운로드 받으려면 다음과 같이 한다.

먼저 판다스를 임포트한다.

import pandas as pd
  

pandasread_csv 함수를 이용하여 데이터를 읽어들인다. read_csv 함수는 파일은 물론 인터넷 주소에서도 데이터를 읽어올 수 있다. sep=';'은 이 파일이 세미콜론(;)으로 구분되어 있기 때문에 지정해주는 것이다.

red_wine = pd.read_csv(
      'https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv',
      sep=';')
  white_wine = pd.read_csv(
      'https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-white.csv',
      sep=';')
  

읽어온 파일의 앞부분을 확인해보자.

red_wine.head()
  
fixed acidity volatile acidity citric acid residual sugar chlorides free sulfur dioxide total sulfur dioxide density pH sulphates alcohol quality
0 7.4 0.70 0.00 1.9 0.076 11.0 34.0 0.9978 3.51 0.56 9.4 5
1 7.8 0.88 0.00 2.6 0.098 25.0 67.0 0.9968 3.20 0.68 9.8 5
2 7.8 0.76 0.04 2.3 0.092 15.0 54.0 0.9970 3.26 0.65 9.8 5
3 11.2 0.28 0.56 1.9 0.075 17.0 60.0 0.9980 3.16 0.58 9.8 6
4 7.4 0.70 0.00 1.9 0.076 11.0 34.0 0.9978 3.51 0.56 9.4 5

데이터의 형태를 확인해보자.

red_wine.shape
  
(1599, 12)

각 데이터프레임에 color 열을 만들어 레드 와인은 1, 화이트 와인은 0으로 와인 색상을 추가한 다음 둘을 합쳐 하나의 데이터프레임으로 만든다.

red_wine['color'] = 1
  white_wine['color'] = 0
  wine = pd.concat([red_wine, white_wine])
  

13.3. 회귀

연속적인 변수를 예측하는 경우를 회귀(regression)라고 한다.

13.3.1. 독립변수와 종속변수

기계학습에서 예측은 독립변수(x)를 이용해 종속변수(y)를 예측한다. 데이터에서 독립변수와 종속변수를 나눠준다. 여기서는 'quality'를 종속변수로 하고, 나머지를 독립변수로 한다.

x = wine[red_wine.columns.difference(['quality'])]
  y = wine['quality']
  

13.3.2. 데이터 분할

기계학습을 할 때는 학습을 시킬 훈련용 데이터와 학습된 성능을 검증할 테스트용 데이터를 나눈다. 아래 코드는 wine 데이터 중 무작위로 20%를 테스트용으로 배정한다. random_state를 아래와 같이 고정해주면 코드를 실행할 때마다 같은 방식으로 데이터를 나눈다.

from sklearn.model_selection import train_test_split
  
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=.2, random_state=1234)
  

13.3.3. 최근접 이웃

먼저 최근접 이웃(k Nearest Neighbors: kNN) 방법을 사용해보자. kNN은 가장 유사한 k개의 사례들을 평균 내는 방식으로 예측을 한다.

from sklearn.neighbors import KNeighborsRegressor
  
knn = KNeighborsRegressor(n_neighbors=5)  # 5개의 유사사례를 참고하는 모형
  

훈련용 데이터를 이용해 학습을 시킨다.

knn.fit(x_train, y_train)
  
KNeighborsRegressor(algorithm='auto', leaf_size=30, metric='minkowski',
            metric_params=None, n_jobs=1, n_neighbors=5, p=2,
            weights='uniform')

이제 테스트 데이터를 이용해 예측을 한다.

y_knn = knn.predict(x_test)
  

13.3.4. 예측과 실제의 비교

먼저 시각화를 통해 예측과 실제를 비교해보자.

%matplotlib inline
  
import seaborn as sns
  
sns.set_style('darkgrid')
  
sns.regplot(x=y_test, y=y_knn, fit_reg=False, x_jitter=0.4, scatter_kws={'alpha': 0.1})
  
<matplotlib.axes._subplots.AxesSubplot at 0x1998657e278>
<matplotlib.figure.Figure at 0x199865866a0>

다음으로는 수치화된 비교를 해보자.

from sklearn.metrics import mean_squared_error, r2_score
  

MSE는 오차의 제곱의 평균이다.

mean_squared_error(y_test, y_knn)
  
0.64267692307692315

MSE 자체만으로는 오차가 얼마나 큰지 알기 어렵기 때문에 실제값의 분산과 비교를 한 R제곱을 많이 사용한다. R제곱은 1 - MSE/분산으로 계산하며 예측이 분산의 몇 %를 설명하는지로 해석한다.

r2_score(y_test, y_knn)
  
0.1617311439983018

13.3.5. 전처리

전처리를 잘 해주면 기계학습의 성능이 향상될 수 있다. 특히 kNN의 경우에는 비슷한 사례를 찾기 때문에 각 변수를 표준화시켜주면 성능이 더 향상된다. 표준화란 변수 값에서 평균을 빼고 표준편차를 나눠서, 모든 변수의 평균이 0, 표준편차가 1이 되도록 조정해주는 것이다.

from sklearn.preprocessing import StandardScaler
  
stand = StandardScaler()
  
x_train_std = stand.fit_transform(x_train)  # 훈련용 데이터를 표준화한다
  x_test_std = stand.transform(x_test)  # 훈련용과 같은 방식으로 변환한다
  

13.3.6. kNN 재학습

다시 모형을 만들어 학습을 시키고 성능을 검증해본다.

knn2 = KNeighborsRegressor(n_neighbors=5)
  
knn2.fit(x_train_std, y_train)
  
KNeighborsRegressor(algorithm='auto', leaf_size=30, metric='minkowski',
            metric_params=None, n_jobs=1, n_neighbors=5, p=2,
            weights='uniform')
y_knn2 = knn2.predict(x_test_std)
  

눈으로 보기엔 큰 차이가 없어보인다.

sns.regplot(x=y_test, y=y_knn2, fit_reg=False, x_jitter=0.4, scatter_kws={'alpha': 0.1})
  
<matplotlib.axes._subplots.AxesSubplot at 0x199864d92e8>
<matplotlib.figure.Figure at 0x19986517358>

지표로 보면 성능이 더 향상된 것을 확인할 수 있다.

r2_score(y_test, y_knn2)
  
0.37813108997240807

13.3.7. 선형 모형 학습

선형 모형은 각 변수에 일정한 가중치를 곱하여 총점을 구하는 방식으로 예측하는 방법이다.

from sklearn.linear_model import LinearRegression
  
lm = LinearRegression()
  
lm.fit(x_train, y_train)
  
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)
y_lm = lm.predict(x_test)
  
sns.regplot(x=y_test, y=y_lm, fit_reg=False, x_jitter=0.4, scatter_kws={'alpha': 0.1})
  
<matplotlib.axes._subplots.AxesSubplot at 0x199865b23c8>
<matplotlib.figure.Figure at 0x199865ed1d0>
r2_score(y_test, y_lm)
  
0.2943666891047606

선형 모형에도 표준화된 데이터를 적용하면 성능이 향상되는지 확인을 해보자.

lm2 = LinearRegression()
  
lm2.fit(x_train_std, y_train)
  
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)
y_lm2 = lm2.predict(x_test_std)
  

표준화 하기 전과 성능에 큰 차이가 없다. 모형에 따라 수학적 특성이 다르기 때문에 전처리의 효과도 다르다.

r2_score(y_test, y_lm2)
  
0.29436668910476005

13.4. 분류

'이것 아니면 저것' 식으로 예측하는 것은 분류라고 한다. 이번에는 레드/화이트 와인을 예측해보자.

xc = wine[red_wine.columns.difference(['color'])]
  yc = wine['color']
  

역시 데이터 분할을 해준다.

xc_train, xc_test, yc_train, yc_test = train_test_split(xc, yc, test_size=.2, random_state=1234)
  

13.4.1. 최근접 이웃

분류에도 최근접 이웃이 있다. 회귀의 경우와 다르게 평균 대신 '다수결'로 예측을 한다. 유사 사례 중에 레드 와인이 많으면 레드로, 화이트 와인이이 많으면 화이트로 예측한다.

from sklearn.neighbors import KNeighborsClassifier
  
knc = KNeighborsClassifier(n_neighbors=5)
  
knc.fit(xc_train, yc_train)
  
KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
             metric_params=None, n_jobs=1, n_neighbors=5, p=2,
             weights='uniform')
y_knc = knc.predict(xc_test)
  

13.4.2. 분류에서 성능 평가

분류에서는 오차가 없기 때문에 MSE나 R제곱 대신 각 사례를 얼마나 잘 분류했는지를 확인한다.

from sklearn.metrics import confusion_matrix
  

먼저 혼돈행렬을 확인해본다. 혼돈행렬은 행이 실제 값을, 열이 예측값을 나타낸다. 즉, 실제로 0이고 예측도 0인 경우가 935건, 실제로 0이지만 1로 예측한 경우가 30건, 실제로 1이지만 0으로 예측한 경우가 43건, 실제로도 1이고 예측도 1인 경우가 292건이다.

confusion_matrix(yc_test, y_knc)
  
array([[935,  30],
         [ 43, 292]])

혼돈 행렬을 이용해 여러 가지 지표를 만들 수 있는데 그 중 대표적인 것이 정확도(accuracy)이다. 정확도는 전체 경우에서 예측과 실제가 일치한 바율을 나타낸다.

from sklearn.metrics import accuracy_score
  
accuracy_score(yc_test, y_knc)
  
0.94384615384615389

13.4.3. 로지스틱 회귀분석

로지스틱 회귀분석은 선형 모형을 분류에 응용한 것이다. 가중치를 곱해서 총점을 구하는 부분까지는 똑같지만 그 총점에 로지스틱 함수를 적용해 0~1 범위의 값으로 예측한다. 만약 예측값이 기준(예: 0.5)보다 크면 1로 작으면 0으로 예측하는 방식이다.

from sklearn.linear_model import LogisticRegression
  
lr = LogisticRegression()
  
lr.fit(xc_train, yc_train)
  
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
            intercept_scaling=1, max_iter=100, multi_class='ovr', n_jobs=1,
            penalty='l2', random_state=None, solver='liblinear', tol=0.0001,
            verbose=0, warm_start=False)
y_lr = lr.predict(xc_test)
  

혼돈 행렬을 만들어본다.

confusion_matrix(yc_test, y_lr)
  
array([[955,  10],
         [ 13, 322]])

정확도를 계산해본다.

accuracy_score(yc_test, y_lr)
  
0.98230769230769233